要给校赛出题了,想出一个挂在服务器端的题目。但是不知道怎么搞,去研究了一下之前SUCTF
的题目源码。
- OS:CentOS 7 x64
- CPU:1 vCore
- RAM:1024 MB
- SSD:25 GB SSD
服务器如何搭建相关环境?
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
|
yum update -y
yum groupinstall -y "Development tools"
yum install -y zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel gmp python-devel socat lrzsz nc
# Install pip
cd /usr/src
curl "https://bootstrap.pypa.io/get-pip.py" -o "get-pip.py"
python2.7 get-pip.py
pip install --upgrade pip
# Install pwntools
pip install --upgrade pwntools
# Install pycrypto
pip install pycryptodome
# Test pycrypto: python -m Cryptodome.SelfTest
# Install numpy
pip install numpy
# -----------------------------------------------------
# For gmpy2
mkdir -p $HOME/src
mkdir -p $HOME/static
# Install m4
v=1.4.18
cd $HOME/src
wget http://ftp.gnu.org/gnu/m4/m4-${v}.tar.gz
tar xf m4-${v}.tar.gz && cd m4-${v}
./configure -prefix=/usr/local
make && make check && make install
# Install gmp
v=6.1.2
cd $HOME/src
wget https://gmplib.org/download/gmp/gmp-${v}.tar.bz2
tar -jxvf gmp-${v}.tar.bz2 && cd gmp-${v}
./configure --prefix=$HOME/static --enable-static --disable-shared --with-pic
make && make check && make install
# Install mpfr
v=4.0.1
cd $HOME/src
wget http://ftp.gnu.org/gnu/mpfr/mpfr-${v}.tar.bz2
tar -jxvf mpfr-${v}.tar.bz2 && cd mpfr-${v}
./configure --prefix=$HOME/static --enable-static --disable-shared --with-pic --with-gmp=$HOME/static
make && make check && make install
# Install mpc
v=1.1.0
cd $HOME/src
wget ftp://ftp.gnu.org/gnu/mpc/mpc-${v}.tar.gz
tar -zxvf mpc-${v}.tar.gz && cd mpc-${v}
./configure --prefix=$HOME/static --enable-static --disable-shared --with-pic --with-gmp=$HOME/static --with-mpfr=$HOME/static
make && make check && make install
# Install gmpy2
v=2-2.1.0a1
cd $HOME/src
wget https://github.com/aleaxit/gmpy/releases/download/gmpy${v}/gmpy${v}.tar.gz
v=2-2.1.0a1
tar xf gmpy${v}.tar.gz && cd gmpy${v}
python setup.py build_ext --static-dir=$HOME/static install
# Setup firewall
# systemctl status firewalld
# systemctl start firewalld
# systemctl unmask firewalld
# firewall-cmd --zone=public --add-port=10001-10005/tcp --permanent
# firewall-cmd --reload
# firewall-cmd --zone=public --list-ports
|
注意Linux
下的换行是\n
,而Win
下的换行是\r\n
,从Win
复制到服务器上的时候要留意一下这个问题。
服务器能运行题目,但是本地连不上服务器?
防火墙端口的问题,需要配置一下防火墙。
- 查看当前防火墙状态:
systemctl status firewalld
- 启动防火墙:
systemctl start firewalld
- 重启防火墙:
systemctl restart firewalld.service
- 停止防火墙:
systemctl stop firewalld.service
- 永远停止防火墙,开机不会启动:
systemctl disable firewalld.service
- 开启某段端口:
firewall-cmd --zone=public --add-port=10001-10005/tcp --permanent
- 关闭某段端口:
firewall-cmd --zone=public --remove-port=10001-10005/tcp --permanent
- 重新加载防火墙(每次开启或者关闭后必须重新加载才有效):
firewall-cmd --reload
- 查看当前开放的端口与协议:
firewall-cmd --zone=public --list-ports
下面复现一道Crypto题的环境,选择SUCTF 2019
的Prime
。
- 首先从GitHub
上下载这场比赛的源码
git clone https://github.com/team-su/SUCTF-2019
- 然后根据上面那个
sh
脚本里面的一些命令安装socat
以及该题用到的Python
库
pycryptodome, numpy, gmpy2(挺麻烦的)
- 给的源码文件
start_from_here.py
里默认开放端口为22222
,可以根据自己的喜好修改
- 开启防火墙相应的端口,并重新加载
systemctl start firewalld
firewall-cmd --zone=public --add-port=22222/tcp --permanent
firewall-cmd --reload
firewall-cmd --zone=public --list-ports
能看到22222/tcp
,说明22222
端口已经开启。
- 在
xxx/SUCTF-2019/Crypto/Prime
目录下运行python start_from_here.py
这样,这一题的环境就成功在我们自己的服务器上面搭好了。
如果是阿里云的服务器,可能还需要在阿里云控制台那里开放一下相应的端口。
现在,我们要从本地去打这一道题。
首先nc ip 22222
尝试一下能否连接到服务器。
能够成功连到,并返回题目内容。
直接用当初打比赛的时候写的exp.py
打一发:
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
|
# python2
from pwn import *
from random import choice
from string import ascii_letters, digits
from hashlib import md5
from Crypto.Util.number import *
r = remote('xxx.xxx.xxx.xxx', 22222)
# context.log_level = 'debug'
def fuck():
r.recvuntil('[*] Please find a string that md5(str + ')
salt, part_hash = r.recvline().strip().split(')[0:5] == ')
# print "salt: " + salt
# print "part_hash: " + part_hash
s = ''
while hashlib.md5(s + salt).hexdigest()[0:5] != part_hash:
s = ''.join([choice(ascii_letters+digits) for _ in range(10)])
r.recvuntil('> ')
r.sendline(s)
fuck()
cs, ns, ms = list(), list(), list()
for _ in range(4):
cs.append(int(r.recvline().strip().split('= ')[-1].strip('L'), 16))
ns.append(int(r.recvline().strip().split('= ')[-1].strip('L'), 16))
for i in range(4):
p0 = GCD(ns[i], ns[(i+1) % 4])
p1 = GCD(ns[i], ns[(i+2) % 4])
p2 = GCD(ns[i], ns[(i+3) % 4])
p3 = ns[i] // p0 // p1 // p2
phi = (p0-1)*(p1-1)*(p2-1)*(p3-1)
e = ns[i] % phi
d = inverse(e, phi)
ms = hex(pow(cs[i], d, ns[i]))
r.recvuntil('ms[%d] = ' % i)
r.sendline(ms)
r.interactive()
|
成功!
也能在服务器端看到log
输出。
下一步就要根据这个模板来自己写一道题了。
Good luck to me.
题目源码:https://github.com/team-su/SUCTF-2019
Centos下配置防火墙:https://www.cnblogs.com/TTyb/p/9871706.html
Linux换行符:https://blog.csdn.net/mulangren1988/article/details/54316783
Crypto库安装:https://blog.csdn.net/zhangpeterx/article/details/96428212
numpy库安装:https://github.com/jupyter/help/issues/141
netcat使用:https://wangjun.dev/2017/11/netcat-general/
rz/sz安装:https://blog.csdn.net/ljxfblog/article/details/38396421
socat使用:https://medium.com/@copyconstruct/socat-29453e9fc8a6
gmpy2库安装:https://www.cnblogs.com/pcat/p/5746821.html
以上是2019年10月份左右写的,下面以2020WMCTF的一道Game题目来展示一下如何使用docker来搭建。
当前目录下的文件:
1
2
3
4
5
6
7
|
~/ctf/problems/wmctf/Game/Crypto_Game/deploy ❯ tree -L 1
.
├── Dockerfile
├── secret.py
└── task.py
0 directories, 3 files
|
要写好task.py
文件(题目具体内容),并把flag放在secret.py
文件中。
只需要把task.py
文件给选手即可,这样选手就可以自己在本地运行了,而且本地运行的时候可以把proof of work
给注释掉。
其中task.py
中的内容为:
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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
|
# !/usr/bin/env python3
import socketserver
import os, sys, signal
import string, random
from hashlib import sha256
from Crypto.Cipher import AES
from secret import flag
BANNER = br"""
___ ___ ___ ___
/\ \ /\ \ /\__\ /\__\
_\:\ \ |::\ \ /:/ / ___ /:/ _/_
/\ \:\ \ |:|:\ \ /:/ / /\__\ /:/ /\__\
_\:\ \:\ \ __|:|\:\ \ /:/ / ___ /:/ / /:/ /:/ /
/\ \:\ \:\__\ /::::|_\:\__\ /:/__/ /\__\ /:/__/ /:/_/:/ /
\:\ \:\/:/ / \:\~~\ \/__/ \:\ \ /:/ / /::\ \ \:\/:/ /
\:\ \::/ / \:\ \ \:\ /:/ / /:/\:\ \ \::/__/
\:\/:/ / \:\ \ \:\/:/ / \/__\:\ \ \:\ \
\::/ / \:\__\ \::/ / \:\__\ \:\__\
\/__/ \/__/ \/__/ \/__/ \/__/
"""
MENU = br"""
1. encrypt
2. guess
3. exit
"""
class Task(socketserver.BaseRequestHandler):
def _recvall(self):
BUFF_SIZE = 2048
data = b''
while True:
part = self.request.recv(BUFF_SIZE)
data += part
if len(part) < BUFF_SIZE:
break
return data.strip()
def send(self, msg, newline=True):
try:
if newline:
msg += b'\n'
self.request.sendall(msg)
except:
pass
def recv(self, prompt=b'> '):
self.send(prompt, newline=False)
return self._recvall()
def recvhex(self, prompt=b'> '):
self.send(prompt, newline=False)
try:
data = bytes.fromhex(self._recvall().decode('latin-1'))
except ValueError as e:
self.send(b"Wrong hex value!")
self.close()
return None
return data
def close(self):
self.send(b"Bye~")
self.request.close()
def pad(self, data):
pad_len = 16 - len(data)%16
return data + bytes([pad_len])*pad_len
def proof_of_work(self):
random.seed(os.urandom(8))
proof = ''.join([random.choice(string.ascii_letters+string.digits) for _ in range(20)])
_hexdigest = sha256(proof.encode()).hexdigest()
self.send(f"sha256(XXXX+{proof[4:]}) == {_hexdigest}".encode())
x = self.recv(prompt=b'Give me XXXX: ')
if len(x) != 4 or sha256(x+proof[4:].encode()).hexdigest() != _hexdigest:
return False
return True
def handle(self):
signal.alarm(1200)
self.send(BANNER)
if not self.proof_of_work():
return
secret = os.urandom(48)
key = os.urandom(16)
IV = os.urandom(16)
aes = AES.new(key, mode=AES.MODE_CBC, iv=IV)
self.send(f"IV is: {IV.hex()}".encode())
self.send(b"Guess the secret, and I will give you the flag if you're right~!")
while True:
self.send(MENU, newline=False)
choice = self.recv()
if choice == b"1":
msg = self.recvhex(prompt=b"Your message (in hex): ")
if not msg: break
cipher = aes.encrypt(self.pad(msg + secret))
self.send(cipher.hex().encode())
continue
elif choice == b"2":
guess = self.recvhex(prompt=b"Your guess (in hex): ")
if not guess: break
if guess == secret:
self.send(b"TQL!!! Here is your flag: " + flag)
else:
self.send(b"TCL!!!")
self.close()
break
class ThreadedServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
pass
class ForkedServer(socketserver.ForkingMixIn, socketserver.TCPServer):
pass
if __name__ == "__main__":
HOST, PORT = '0.0.0.0', 10000
server = ForkedServer((HOST, PORT), Task)
server.allow_reuse_address = True
server.serve_forever()
|
其中大部分内容也都是照着别人的模版来写的。
secret.py
文件中就是定义了一个flag:
1
|
flag = b"WMCTF{Dont_ever_tell_anybody_anything___If_you_do__you_start_missing_everybody}"
|
Dockerfile配置如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
FROM python:3.8-alpine
LABEL Description="Game" VERSION='1.0'
RUN apk update && apk add gcc g++ make openssl-dev python3-dev libffi-dev autoconf
WORKDIR /opt/game
RUN mkdir -p /opt/game
COPY task.py .
COPY secret.py .
RUN pip install pycryptodome
EXPOSE 10000
CMD ["python", "-u", "task.py"]
|
其实也很简单,使用了一个python3.8的镜像,然后安装了一下依赖和Crypto库,再把当前目录下的那3个文件全部copy到docker镜像中,并开放10000端口,最后运行task.py
文件启动TCP服务。
把这3个文件给运维就可以完事了。
不过也可以先本地测试一下是否ok:
-
docker build . -t wmctf-game
build一下镜像
-
docker images
检查一下
-
docker run --name game -d -p 10000:10000 wmctf-game
创建一个container容器来运行
其中,-d
表示run了之后不进入到container内部,-p 10000:10000
将container内部的10000端口映射出来到本地的10000端口
-
docker container ls
可以看到已经有一个container在运行了
-
nc 127.0.0.1 10000
即可连接到题目的TCP服务