ctfshow-新手杯-crypto4新手村难度-wp

ctfshow-新手杯

先贴一贴官方wp

国庆放假摆得太厉害,忘记了还有比赛,放完后赶紧回来补一补

crypto4 新手村难度

贴贴题目

from Crypto.Util.number import *
from sympy import totient as phi
import sys
import random

def pr(x, end='\n'):
    sys.stdout.write(f'{x}{end}')
    sys.stdout.flush()

def newbie(naive, p):
    if (len(naive) == 0):
        return 1
    NB = 1
    PH = phi(p) # AAHPH
    for i in range(len(naive)-1, -1, -1):
        if (NB > 4396): # clearlove picked the blind monk
            return pow(naive[0], int(newbie(naive[1:], PH) + PH), int(p))
        NB = naive[i] ** NB
    return NB % p

BANNER = '''
+-------------------+
|     *             |
|     **     ****** |
|  ******** ******* |
|  ******** ***     |
|   *    ** ***     |
|    *  *** ***     |
| *************     |
| ***************** |
|     **    ******* |
|     **    *** **  |
|  ******** *** **  |
|     **    *** **  |
|   * ****  *** **  |
|  ** ** ** **  **  |
|  *  **  ****  **  |
| **  **   ***  **  |
|   ****  **    **  |
|                   |
+-------------------+

比赛名称:新手杯
比赛时间:2022年10月3日20时,共48小时
比赛类型:个人赛
比赛难度:新手村难度
比赛奖品:神兽抱枕(女用)
题目投稿:ctfshow@163.com
投稿奖品:神兽抱枕(男用)
投稿截止:10月1日20时
'''

pr(BANNER)

p = getPrime(64)
while True:
    p = getPrime(64)
    if (p % 15 == 2):
        break

with open('flag.txt', 'rb') as f:
    flag = f.read()
flag = f'0{bytes_to_long(flag):b}'

pr(f'{p = }')
pr(f'{len(flag) = }')
while True:
    pr('> ', end='')
    seed = int(input())
    random.seed(seed)
    newbie_flag = list(flag)
    random.shuffle(newbie_flag)
    sometimes_naive = [int(x) * 2 + 3 for x in newbie_flag]
    pr(f'{newbie(sometimes_naive, p) = }')

对照着官方 w p wp wp一顿复现,结果发现 w p wp wp都没怎么看得懂,没想通二次剩余是怎么用的,终究还是我太菜了

先瞅瞅题目整个流程(简化题目,简化问题)

f l a g flag flag的操作:将 f l a g flag flag转成0/1的比特流,然后用 r a n d o m . s h u f f l e ( ) random.shuffle() random.shuffle()打乱顺序,接着将 ( 0 , 1 ) (0,1) (0,1)分别映射到 ( 3 , 5 ) (3,5) (3,5)上,最后丢到 n e w b i e ( ) newbie() newbie()里面去

再瞧瞧 n e w b i e ( ) newbie() newbie()函数:不如看 w p wp wp分析,我讲不出什么名堂来,不过可以确定的是 n e w b i e ( ) newbie() newbie()函数的返回值是 S 0 S 1 S_0^{S_1} S0S1,看得更简单些就是 3 2 ∗ k + 1 3^{2*k+1} 32k+1 5 2 ∗ k + 1 5^{2*k+1} 52k+1

【提示】crypto4 提示为:幂次必为奇数

我自己的想法是既然幂次必为奇数,那么如果 3 2 ∗ k + 1 3^{2*k+1} 32k+1 5 2 ∗ k + 1 5^{2*k+1} 52k+1再乘以底数,幂次就变成了偶数( 2 ∗ k + 2 2*k+2 2k+2),这样就可以很合理地有限域开方。如果乘 3 3 3以后能开出来那么底就是 3 3 3,那个 b i t bit bit就为 0 0 0了…这样就求出了第一位的值

利用 s h u f f l e shuffle shuffle来让不同位置的都变成 a 0 a_0 a0来得到所有bit的值

题目需要输入 s e e d seed seed,可以控制这样的伪随机,多变变 s e e d seed seed来得到所有位置bit的值

先贴贴exp1

p % 4 != 3会挂掉,要重新跑,懒得写while true try了,将就用吧

拿带pwntools的sage跑,如果sage里面没有pwntools可以分两段,先拿有pwntools的python拿下newbie的值,然后再用sage

#!usr/bin/python3
# -*- coding: utf-8 -*-
# @Time    : 2022/10/8 19:20
# @Author  : mxx307
# @FileName: newbie.py
# @Software: PyCharm

import random
import tqdm
from pwn import *
from Crypto.Util.number import *

re = remote('pwn.challenge.ctf.show', 28104)
tmp = re.recvuntil(b'p = ')
p = int(re.recvline().decode()[:-1])
if p % 4 != 3:
    re.close()
    exit()
tmp = re.recvuntil(b'len(flag) = ')
flag_len = int(re.recvline().decode()[:-1])
print(p,flag_len)
# flag_len = 368
flag = [-1 for i in range(flag_len)]
# C = []
INDEX = []
SEED = []
seed = 0
while len(SEED) != flag_len:
    random.seed(seed)
    randlist = list(range(flag_len))
    random.shuffle(randlist)
    indexx = list(range(flag_len)).index(randlist[0])
    if indexx not in INDEX:
        INDEX.append(indexx)
        SEED.append(seed)
    seed += 1
assert len(SEED) == flag_len
for i in tqdm.tqdm(range(len(SEED))):
    tmp = re.recvuntil(b'> ')
    re.sendline(str(SEED[i]).encode())
    tmp = re.recvuntil(b'newbie(sometimes_naive, p) = ')
    c = int(re.recvline().decode()[:-1])
    # C.append(c)
    PR.<x> = Zmod(int(p))[]
    f1 = x * x - 3 * c
    f2 = x * x - 5 * c
    res1 = f1.roots()
    res2 = f2.roots()
    if res1 and res2 == []: flag[INDEX[i]] = 0
    elif res1 == [] and res2: flag[INDEX[i]] = 1
    if flag.count(-1) == 0:
        flag = [str(i) for i in flag]
        print(long_to_bytes(int(''.join(flag),2)))
        break
re.close()

# print(f'{C = }')
# from Crypto.Util.number import *
# flag = [-1 for i in range(flag_len)]
# for i in range(len(C)):
#     c = C[i]
#     PR.<x> = Zmod(int(p))[]
#     f1 = x * x - 3 * c
#     f2 = x * x - 5 * c
#     res1 = f1.roots()
#     res2 = f2.roots()
#     if res1 and res2 == []: flag[INDEX[i]] = 0
#     elif res1 == [] and res2: flag[INDEX[i]] = 1
#     if flag.count(-1) == 0:
#         flag = [str(i) for i in flag]
#         print(long_to_bytes(int(''.join(flag),2)))
#         break

w p wp wp回过头来思考二次剩余怎么用上去(数论没学好)

根据 w p wp wp,若有 p ≡ 3   ( m o d   4 ) p\equiv3\ (mod\ 4) p3 (mod 4),那么 ( 3 p ) = 1 (\frac{3}{p})=1 (p3)=1,即 3 3 3是模 p p p的二次剩余。既然 3 3 3是二次剩余,那么 3 k 3^k 3k呢?那必须也得是啊,无论 k k k是奇数还是偶数,那么意味着 3 2 ∗ k + 1 3^{2*k+1} 32k+1可以被有限域开方求根,但是 5 2 ∗ k + 1 5^{2*k+1} 52k+1不行(所以用上面那个exp直接对c开方,能开出来就是3,开不出来就是5,这样也能求,对开方情有独钟)

再想想有没有啥办法不求根就能判断是否为二次剩余呢?

嗯~ o( ̄▽ ̄)o,我想不到,于是

image-20221009131455394

随便找了一篇,看到了Euler判别准则(欧拉准则)

对于奇素数 p p p p ∤ a p\nmid a pa p p p不能被 a a a整除)有
a ( p − 1 ) / 2 ≡ ( a p ) ≡ { 1   ( m o d   p ) , 若 x 2 ≡ a   ( m o d   p ) 有解 − 1   ( m o d   p ) , 若 x 2 ≡ a   ( m o d   p ) 无解 a^{(p-1)/2} \equiv \left(\frac{a}{p}\right) \equiv \left\{\begin{aligned}1\ (mod\ p) ,若x^2\equiv a\ (mod\ p)有解 \\-1\ (mod\ p),若x^2\equiv a\ (mod\ p)无解\end{aligned} \right. a(p1)/2(pa){1 (mod p),x2a (mod p)有解1 (mod p),x2a (mod p)无解
判断是否为二次剩余确定是 3 3 3还是 5 5 5

酱紫就简单了些,可能这才是预期解吧(maybe)

最喜欢春哥出的题目辣,每次都能学到很多,这次学到了二次剩余+欧拉准则+高斯二次互反律

春哥yyds

贴贴expplus

p % 4 != 3会挂掉,要重新跑,懒得写while true try了,将就用吧

#!usr/bin/python3
# -*- coding: utf-8 -*-
# @Time    : 2022/10/9 13:29
# @Author  : mxx307
# @FileName: expplus.py
# @Software: PyCharm

import random
import tqdm
from pwn import *
from Crypto.Util.number import *

re = remote('pwn.challenge.ctf.show', 28105)
re.recvuntil(b'p = ')
p = int(re.recvline().decode()[:-1])
if p % 4 != 3:
    re.close()
    exit()
re.recvuntil(b'len(flag) = ')
flag_len = int(re.recvline().decode()[:-1])
print(p, flag_len)
flag = [-1 for i in range(flag_len)]
INDEX = []
SEED = []
seed = 0
while len(SEED) != flag_len:
    random.seed(seed)
    randlist = list(range(flag_len))
    random.shuffle(randlist)
    indexx = list(range(flag_len)).index(randlist[0])
    if indexx not in INDEX:
        INDEX.append(indexx)
        SEED.append(seed)
    seed += 1
assert len(SEED) == flag_len
for i in tqdm.tqdm(range(len(SEED))):
    re.recvuntil(b'> ')
    re.sendline(str(SEED[i]).encode())
    re.recvuntil(b'newbie(sometimes_naive, p) = ')
    c = int(re.recvline().decode()[:-1])
    check = pow(c, (p - 1) // 2, p)
    flag[INDEX[i]] = 0 if check == 1 else 1
flag = [str(i) for i in flag]
print(long_to_bytes(int(''.join(flag), 2)))
 
re.close()

image-20221009134103089

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mxx307

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值