Interactive_Cryptography

经过一个假期的不断摸索,终于把Crypto交互的部分搞明白了。本篇记录了Crypto交互题的解题过程,以及一道交互题是如何实现其功能的。

0x1. 交互题概述

交互题的实质,就是挂载在服务器的一个端口上的程序。解交互题时,一般给选手一个nc码,包含服务器ip以及端口号,选手在控制台输入nc码,即可进入与服务器的交互。有时候题目附件还包含服务器端的代码,方便选手查看加密过程,但严格限制选手的权限,不允许选手查看flag的内容。

0x2. 解交互题

0x2.1 编写交互脚本

对于交互次数较少的题目,当然可以选择不写交互脚本,但如果交互次数较多,每次单独执行一遍exp就会很麻烦,这时候就要编写自动化交互脚本。

交互脚本需要用到python的pwn库,包含各种能实现收、发包的函数。

1
conn=remote('HOST', PORT)

初始化conn,确认要与哪个服务器的哪个端口交互。

1
conn.recv()

一次接收到的内容,不对换行符敏感,内容的类型为bytes

1
conn.recvline()

一次接收到的内容,以换行符为结束,类型也是bytes

1
conn.sendline(b'aaa')

向服务器发送指定内容,对于int类型要变换为bytes类型:f'{x}'.encode

1
conn.sendlineafter(b'aaa',b'bbb')

在收到特定的字节流后发送指定内容,搭配上一条使用。

0x2.2 特别注意

第一,要准确把握程序在一个循环的每个阶段输的内容,才能正确编写交互脚本,否则要么报错,要么无法返回正确答案。有的题目非常操蛋,跟鸡拉屎一样的一截一截的发,那就只能一段一段的recvline(),而不是recv().split('\n')。如果没有附件,最好一步步打印输出内容。

交互过程中有两个特殊部分,就是交互最开始和最末尾。最开始不会像交互过程中返回一个Yes或者No,因此要单独执行再进入循环(循环可以用while True,因为你不知道要执行多少次)。而最末尾输出正确后就直接给flag了,而交互脚本还在解码,所以大概率要报错,因此无论返回值是啥都要先print再继续。

第二,交互脚本只是工具,正确的攻击思想和严密的逻辑才是解答密码题的核心!(打枪没有用的.jpg)

0x3 交互题是怎样炼成的

0x3.1 概述

1
socat -d -d tcp-l:|port| ,reuseaddr,fork EXEC:"python -u server.py"

socat直接挂载,一步到位,非常无脑,只使用于自己玩。

出题面临的情况是:服务器没有python,也没有python的那些库函数;运维是一只会敲打键盘的猩猩,除了按照指示在控制台上输入命令啥也不会。总之,要在一个光板机上用最简单的方式搭建题目环境。不过,情况也没那么糟糕,因为主机上一般有一个很重要的应用:Docker。

61d7ee0b2cab4295.jpg

docker的logo是一个巨鲸,它上面托运了很多集装箱;可以把鲸鱼看成是货轮,相当于是一个平台,上面放的集装箱可以看成是容器,集装箱容器里面装的就是各种项目,而且集装箱与集装箱之间没有任何联系,它们是相互隔离的。

Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口(类似 iPhone 的 app),更重要的是容器性能开销极低。

Dockerfile 是一个用来构建镜像的文本文件,文本内容包含了一条条构建镜像所需的指令和说明。

0x3.2 出发了!(日丹诺夫并感)

交互题server.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
from hashlib import sha256
import socketserver
from secret import flag
import signal
import string
import random
import os
import gmpy2


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 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'[+] Plz tell me XXXX: ')
if len(x) != 4 or sha256(x+proof[4:].encode()).hexdigest() != _hexdigest:
return False
return True

def handle(self):
signal.alarm(60)
self.send('This is a test of gmpy2')
x=gmpy2.powmod(2,3,3)
self.send(b'gmpy2.powmod(2,3,3)='+f'{x}'.encode())

if not self.proof_of_work():
self.send(b'[!] Wrong!')
return

self.send(b'here is your flag')
self.send(flag)


class ThreadedServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
pass


class ForkedServer(socketserver.ForkingMixIn, socketserver.TCPServer):
pass


if __name__ == "__main__":
HOST, PORT = '0.0.0.0', 10001
server = ForkedServer((HOST, PORT), Task)
server.allow_reuse_address = True
print(HOST, PORT)
server.serve_forever()

Dockerfile,在服务器端运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
FROM python:3.8
LABEL Description="baby_try" VERSION='1.0'

COPY server.py .
COPY secret.py .

RUN pip install --upgrade pip
RUN pip install gmpy2
RUN chmod +x server.py

EXPOSE 12345

CMD ["python", "server.py"]

docker-compose.yml,简化创建镜像和启动容器的步骤

1
2
3
4
5
6
7
8
9
version: '3'
services:
checkin:
image: signin
build: .
ports:
- "9999:10001"
restart: always

输入命令一键搭建:docker-compose up -d