tl2cents

  • Home
  • Archives
  • Categories
Toc Friends About me

tl2cents

  • Home
  • Archives
  • Categories

2021-DASCTF-July-WriteUp

2021-08-02
字数统计: 2.4k字   |   阅读时长≈ 12分

本来想看看reverse,结果没啥基础题,就只能摸鱼打比赛了。几道MISC过于脑洞。

BlockTrick

服务器源码 (自己改了py3的本地环境跑):

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
from Crypto.Cipher import AES
import os


def pad(a):
size = (16-len(a) % 16) % 16
a += bytes([size])*size
print(a)
return a


iv = os.urandom(16)
key = os.urandom(16)
enc = AES.new(key, AES.MODE_CBC, iv)
print(iv.hex())


for _ in range(2):
trick = input().strip('\n')
trick = pad(bytes.fromhex(trick))
print(trick)
cipher = enc.encrypt(trick)
if trick == cipher and trick != b"":
with open("flag.txt") as f:
print(f.read())
exit()
else:
print(cipher.hex())
print("Try again")

注意这里CBC模式下,你两次加密也会是一样地按照CBC模式,第二次的IV是上一次的密文结果。分组加密相关的trick,CBC模式下,前一段加密输出的密文会作为后一段明文的异或向量,随后再进行AES加密。首先我们输入给出的iv初始值即可,这样异或后AES加密的值时全0,得到AES对’\x00’* 16的加密。随后我们再将这个输出作为输入,与’\x0O’的密文相互异或,由于我们的输入也是’\x00’的密文,因此再次得到一组’\x00’,随后送入AES进行加密得到’\x00’的密文,而我们的输入正是’\x00’的密文。简单来说,复读即可,操作如下:

QQ截图20210731124321

 
 

MISC

前面放几道纯脑洞,后面放的超级简单的题。无语子。

red_vs_blue

简单的交互题目,猜对66轮红蓝对抗的结果,试了几次发现每次连接上去给的答案是固定的,错了还可以重来,但是断开连接结果就不一样了,规定时间内爆破出答案即可:

脚本

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
from pwn import remote
socket = remote('node4.buuoj.cn', '29691')
res=''
for i in range(66):
m=socket.recvuntil("choose one [r] Red Team,[b] Blue Team:")
# print("p1 ",m)
socket.sendline("b")
socket.recvline()
socket.recvline()
socket.recvline()
msg=socket.recvline()
print("p2 ",msg)
if b'Sorry' in msg:
socket.recvuntil('Play again? (y/n):')
socket.sendline('y')
for j in range(i):
# print("here1")
socket.recvuntil("choose one [r] Red Team,[b] Blue Team:")
socket.sendline(res[j])
# print("here")
res+="r"
socket.recvuntil("choose one [r] Red Team,[b] Blue Team:")
socket.sendline('r')
else:
res+="b"
print(socket.recvall())

得到flag: flag{41ef454c-a406-485d-8933-184f7ab237b8}

image-20210802150153284

 
 

funny_maze

生成迷宫,每次只需要计算出走出迷宫需要的最短距离即可,DFS或者BFS搜索即可,比赛没有自己写脚本,找了现成的脚本,但是得注意下pwntool交互下接受的都是byte即可:

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
from pwn import remote
from pwnlib import flag
import queue
MAX_VALUE = 0x7fffffff

class Point:
def __init__(self, x=0, y=0):
self.x = x
self.y = y

MAX_VALUE = float('inf')

class Point:
def __init__(self, x=0, y=0):
self.x = x
self.y = y

def bfs(maze, begin, end):
n, m = len(maze), len(maze[0])
dist = [[MAX_VALUE for _ in range(m)] for _ in range(n)]
pre = [[None for _ in range(m)] for _ in range(n)] # 当前点的上一个点,用于输出路径轨迹

nx = [[1,0],[-1,0],[0,-1],[0,1]]

sx, sy = begin.x, begin.y
gx, gy = end.x, end.y

dist[sx][sy] = 0
q = queue.Queue()
q.put(begin)
while q:
point = q.get()
if point.x == gx and point.y == gy:
break
for i in range(4):
dx, dy = point.x + nx[i][0], point.y + nx[i][1]
# print(dx,dy,maze[dx][dy],ord('.'))
if 0<=dx<n and 0<=dy<m and maze[dx][dy] != ord('#') and dist[dx][dy] == MAX_VALUE:
dist[dx][dy] = dist[point.x][point.y] + 1
pre[dx][dy] = point
q.put(Point(dx, dy))
stack = []
curr = end
while True:
stack.append(curr)
if curr.x == begin.x and curr.y == begin.y:
break
prev = pre[curr.x][curr.y]
curr = prev
dis=0
while stack:
curr = stack.pop()
dis+=1
print('(%d, %d)' % (curr.x, curr.y))
return dis

socket = remote('node4.buuoj.cn', '27844')
socket.recvuntil("3.Introduction to this game")
socket.sendline("1")
for i in range(100):
# print("p1 ",m)
maze = []
line = socket.recvline()
while b"Please enter your answer:" not in line:
if b"#" not in line:
line = socket.recvline()
continue
maze.append(line.strip(b'\n').replace(b" ",b"."))
line = socket.recvline()
for line in maze:
print(line.decode("utf8"))
n, m = len(maze), len(maze[0])
print(n,m)
begin = Point()
end = Point()
for i in range(n):
s = maze[i]
if b'S' in s:
begin.x = i
begin.y = s.index(b'S')
if b'E' in s:
end.x = i
end.y = s.index(b'E')
print(begin.x,begin.y,end.x,end.y)
dis = bfs(maze, begin, end)
print(dis)
socket.sendline(str(dis))
m = socket.recvline()
m = socket.recvline()
print(m)
if b'walked out of the maze!' in m:
print(socket.recvall())
break
else:
socket.recvuntil(b'move on to the next level!')

 
 

ezSteganography

这是唯一一道隐写没有特别多脑洞的题,其他题目过于脑洞,有点浪费时间。

原始png图片如下:

pngcheck,strings,binwalk之类的都没看出什么花里胡哨的东西,只能放stegsolve里面看看了:

red0 通道发现猫腻:

image-20210802151048512

单独看G plane通道的图片没有发现有用信息,应该就是G通道的LSB隐写,果然发现png头:

image-20210802151210230

提取出来保存为png,得到第一部分

image-20210802151448162

提示了QIM,quantization index modulation,一种加水印的方法,github上面有提供脚本,它针对的是向量处理的,设置步长为20,提取水印如下

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
from __future__ import print_function
import sys
from PIL import Image
import numpy as np
img=Image.open("ezSteganography-flag.png")
# img=Image.open("g_plane.png")
img=np.array(img)

class QIM:
def __init__(self, delta):
self.delta = delta

def embed(self, x, m):
"""
x is a vector of values to be quantized individually
m is a binary vector of bits to be embeded
returns: a quantized vector y
"""
x = x.astype(float)
d = self.delta
y = np.round(x/d) * d + (-1)**(m+1) * d/4.
return y

def detect(self, z):
"""
z is the received vector, potentially modified
returns: a detected vector z_detected and a detected message m_detected
"""

shape = z.shape
z = z.flatten()

m_detected = np.zeros_like(z, dtype=float)
z_detected = np.zeros_like(z, dtype=float)

z0 = self.embed(z, 0)
z1 = self.embed(z, 1)

d0 = np.abs(z - z0)
d1 = np.abs(z - z1)

gen = zip(range(len(z_detected)), d0, d1)
for i, dd0, dd1 in gen:
if dd0 < dd1:
m_detected[i] = 0
z_detected[i] = z0[i]
else:
m_detected[i] = 1
z_detected[i] = z1[i]

z_detected = z_detected.reshape(shape)
m_detected = m_detected.reshape(shape)
return z_detected, m_detected.astype(int)

def random_msg(self, l):
"""
returns: a random binary sequence of length l
"""
return np.random.choice((0, 1), l)


def test_qim():
"""
tests the embed and detect methods of class QIM
"""
delta = 20 # quantization step
qim = QIM(delta)
z_detected, msg_detected = qim.detect(img.astype(float))
# print(z_detected)
print(msg_detected.shape)
im = Image.fromarray(msg_detected.astype('uint8')).convert('RGB')
im.show()
im.save("p2.png")

def main():
test_qim()


if __name__ == "__main__":
sys.exit(main())

当然,提取出来的水印看上去是全黑的,注意前面的提示是somethings in G plane,因此再看看该水印的G plane,得到第二部分

image-20210802152042775

flag{2e9ec6480d05150c211963984dcbc9f1}

 
 

Just A GIF

一张gif,identify看一下

1
$ identify -format "%s %T \n" Just_a_GIF.gif

451张图片,间隔均是10,。

ffmpeg分解gif图片,或者找一个在线网站分解也一样

1
$ ffmpeg -i Just_a_GIF.gif /images/image%d.jpg

如下图,11张一个周期,后面就是脑洞试一试了,肯定不同周期内图片会有差异

image-20210806170519190

比较部分结果如下,可以发现,每张图片之间有部分像素点存在差异,并且只是象征性地加一(255则减一),可以试一下把这些不同的像素点拼起来(反正就硬猜,就很没意思)。每个周期内图片都与第一个周期内图片比较。

1
2
3
4
5
6
7
8
9
----------------compare 1 and 12----------------------
diff find in (0, 52), and pix1 is (253, 251, 250, 255), pix2 is (254, 252, 251, 255)
diff find in (0, 53), and pix1 is (253, 251, 250, 255), pix2 is (254, 252, 251, 255)
diff find in (0, 59), and pix1 is (254, 250, 254, 255), pix2 is (255, 251, 255, 255)
diff find in (1, 0), and pix1 is (255, 255, 255, 255), pix2 is (254, 254, 254, 255)
diff find in (1, 59), and pix1 is (250, 250, 250, 255), pix2 is (251, 251, 251, 255)
diff find in (2, 76), and pix1 is (254, 254, 254, 255), pix2 is (255, 255, 255, 255)
diff find in (2, 82), and pix1 is (254, 254, 254, 255), pix2 is (255, 255, 255, 255)
...

提取如下:

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
from PIL import Image
s = Image.open('images/Just_a_GIF-1.png').size

def compare_img(pic1, pic2,image3):
image1 = Image.open(pic1)
image2 = Image.open(pic2)
width = image1.size[0]
height = image1.size[1]
for w in range(width):
for h in range(height):
p1=image1.getpixel((w, h))
p2=image2.getpixel((w, h))
if p1 != p2:
image3.putpixel((w,h),(0,0,0))
return image3

if __name__ == '__main__':
res=[]
for i in range(11):
res= Image.new('RGB', (s[0], s[1]), (255, 255, 255))
for j in range(40):
pic1='images/Just_a_GIF-'+str(i+1)+'.png'
pic2='images/Just_a_GIF-'+str(i+12+j*11)+'.png'
image1 = Image.open(pic1)
image2 = Image.open(pic2)
print(
"----------------compare {0} and {1}----------------------".format(str(i+441), str(i+1+j*11)))
l=compare_img('images/Just_a_GIF-'+str(i+1)+'.png',
'images/Just_a_GIF-'+str(i+12+j*11)+'.png',res)
res.show()
res.save("extract"+str(i)+".png")

结果如下,得到9张碎片图和两张拼接顺序图

image-20210806171149399

之后拼接起来是DataMatrix,反正不知道是啥,去barcode的online网站扫一下就出来了。

 
 

WEB

cat flag

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php

if (isset($_GET['cmd'])) {
$cmd = $_GET['cmd'];
if (!preg_match('/flag/i',$cmd))
{
$cmd = escapeshellarg($cmd);
system('cat ' . $cmd);
}
} else {
highlight_file(__FILE__);
}
?>

神奇的绕过姿势,给了提示管理员曾经访问过flag,那么在访问日志里面应该有记录,一般网站用的是Nginx文件系统,去找对应的文件系统日志存放路径即可:

日志目录:/var/log/nginx/access.log

image-20210803095628409

可以看到对应flag文件的文件名,想要绕过正则并且正确执行cat this_is_final_flag_e2a457126032b42d.php。

这里关键的一点是 escapeshellarg($cmd)这个函数,虽然这个函数限制了我们只能传一个参数,并且会过滤引号之类的符号,防止执行其他指令,但是它同时会直接去掉非ASCII值的字节,因此我们可以用这个绕过正则匹配,在flag中间插一个非ASCII值的字节即可。

1
2
3
http://1ab97075-546e-4d38-af96-eefb21dfd83b.node4.buuoj.cn/?this_is_final_fl%81ag_e2a457126032b42d.php
-->
<?php $f1ag='f1ag{2c06119c-4f2c-4587-9d59-71ecaecddd63}’;?>
  • CTF
  • writeup
  • CTF

扫一扫,分享到微信

Hacker Game 2021 Writeup
Logistic Regression and Stochastic Gradient Descent
  1. 1. BlockTrick
  2. 2. MISC
    1. 2.1. red_vs_blue
    2. 2.2. funny_maze
    3. 2.3. ezSteganography
    4. 2.4. Just A GIF
  3. 3. WEB
    1. 3.1. cat flag
© 2021-2025 tl2cents
GitHub:hexo-theme-yilia-plus by Litten
  • Toc
  • Friends
  • About me

tag:

  • CTF
  • writeup
  • crypto
  • solidity
  • PQC
  • MPKC
  • Backdoor
  • Dual EC
  • FHE
  • BFV
  • USTC
  • Machine Learning
  • TFHE
  • fibonacci
  • 教程
  • 0S2021
  • Zcash
  • Cryptocurrency
  • Code
  • Lattice
  • ISD
  • Matrix Group
  • Discrete Logarithm

    缺失模块。
    1、请确保node版本大于6.2
    2、在博客根目录(注意不是yilia-plus根目录)执行以下命令:
    npm i hexo-generator-json-content --save

    3、在根目录_config.yml里添加配置:

      jsonContent:
        meta: false
        pages: false
        posts:
          title: true
          date: true
          path: true
          text: false
          raw: false
          content: false
          slug: false
          updated: false
          comments: false
          link: false
          permalink: false
          excerpt: false
          categories: false
          tags: true
    

  • NeSE
  • USTC-Life
  • Xuzzz

Hi, this is tl2cents
Currently Ph.D at IIE of UCAS
Graduated from USTC (2018-2022)

CTFer
Focus on blockchain, crypto and reversing
Team member of NeSE and Nebula

Research
Blockchain | ETH | FHE
PQC | Cryptography | Android

Contact Me
echo Y2F4dWxhc2FlbGFAb3V0bG9vay5jb20K | base64 -d

Thanks for JoeyBling and Litten