强网先锋

baby_crt

考点是 CRT-RSA,找到一篇paper:Wagner’s Attack on a Secure CRT-RSA Algorithm Reconsidered

然后看到里面提到可以这样获取 $p$:

$$ \large gcd(m^{c_1}-Sig^e,N)=p $$

这个题目只有 $c_1$ 没有给出,但是很小,可以直接爆破。

from Crypto.Util.number import *
from hashlib import sha1

e = 65537
n = 26318358382258215770827770763384603359524444566146134039272065206657135513496897321983920652242182112479484135343436206815722605756557098241887233837248519031879444740922789351356138322947108346833956405647578838873425658405513192437479359531790697924285889505666769580176431360506227506064132034621123828090480606055877425480739950809109048177976884825589023444901953529913585288143291544181183810227553891973915960951526154469344587083295640034876874318610991153058462811369615555470571469517472865469502025030548451296909857667669963720366290084062470583318590585472209798523021029182199921435625983186101089395997
m = 26275493320706026144196966398886196833815170413807705805287763413013100962831703774640332765503838087434904835657988276064660304427802961609185997964665440867416900711128517859267504657627160598700248689738045243142111489179673375819308779535247214660694211698799461044354352200950309392321861021920968200334344131893259850468214901266208090469265809729514249143938043521579678234754670097056281556861805568096657415974805578299196440362791907408888958917063668867208257370099324084840742435785960681801625180611324948953657666742195051492610613830629731633827861546693629268844700581558851830936504144170791124745540
sig = 20152941369122888414130075002845764046912727471716839854671280255845798928738103824595339885345405419943354215456598381228519131902698373225795339649300359363119754605698321052334731477127433796964107633109608706030111197156701607379086766944096066649323367976786383015106681896479446835419143225832320978530554399851074180762308322092339721839566642144908864530466017614731679525392259796511789624080228587080621454084957169193343724515867468178242402356741884890739873250658960438450287159439457730127074563991513030091456771906853781028159857466498315359846665211412644316716082898396009119848634426989676119219246

for c1 in range(1, 65536):
    p = GCD(pow(m, c1, n) - pow(sig, e, n), n)
    if p == 1:
        continue
    print(p)
    break

q = n // p
flag = "flag{" + sha1(long_to_bytes(p if p < q else q)).hexdigest() + "}"
print(flag)
# flag{601cb6f6d990ed5b89cf0de60508a95c07543793}

bank

proof_of_work:

from hashlib import sha256
from string import digits, ascii_letters
from pwn import *

r = remote("39.101.134.52", "8005")

def proof_of_work():
    rev = r.recvuntil("sha256(XXX+")
    suffix = r.recv(17).decode()
    rev = r.recvuntil(" == ")
    tar = r.recv(64).decode()

    def f(x):
        hashresult = sha256(x.encode()+suffix.encode()).hexdigest()
        return hashresult == tar
    prefix = util.iters.mbruteforce(f, digits + ascii_letters, 3, 'upto')
    r.recvuntil("Give me XXX:")
    r.sendline(prefix)


def send_teamtoken():
    r.recvuntil("teamtoken:")
    r.sendline("icqc487d794f00cdb22409bd5ea7e736")


proof_of_work()
send_teamtoken()
r.interactive()

连上去过完 proof of work,输入一个字符串作为名字,会给出余额和菜单:

your cash:10 you can choose: transact, view records, provide a record, get flag, hint

试了一下发现可以向某个商人交易,例如 Alice 1 向 Alice 支付,然后会通过 hint 里面的函数生成这次交易的记录,同时我们也可以给他发送一条记录来伪造一次交易。有了足够的余额(1000)就可以买 flag 了。

但是,出题人好像没滤交易时的负数?然后….

可以交易负金额可还行,就拿到了 flag。。。

Web

dice2cry

题目描述:web+cry,输入team_token进入一个掷骰子的页面,在cookie可以看到encrypto_flagpublic_npublic_e,应该是 RSA,然后每次掷骰子都会向abi.php get一次数据,abi.php也可以单独调用,相当于于一个随机返回 0~2 整数的 api,以 json 的形式返回值。然后有 js 来操作一下返回 1~6 的点数。

然后可以在 http://106.14.66.189/abi.php.bak 拿到 abi.php 的源码:

<?php
session_start();
header("Content-type:text/html;charset=utf-8");

        $data = json_decode($json_string, true);

        $rand_number = isset($_POST['this_is.able']) ? $_POST['this_is.able'] : mt_      rand();
        $n = gmp_init($data['n']);
        $d = gmp_init($data['d']);
        $c = gmp_init($rand_number);
        $m = gmp_powm($c,$d,$n);
        $v3 = gmp_init('3');
        $r = gmp_mod($m,$v3);
        $result=(int)gmp_strval($r);
        $dice = array("num"=>$result);
        $json_obj = json_encode($dice);
        echo $json_obj;
?>

如果没有 post 一个数字,$rand_number 就是随机的,否则就是 post 的那个数字,所以 $rand_number 是可控的。

然后服务端会通过私钥 d 对 $rand_number 解密,并返回解密后模 3 的值,也就是一开始看到的 0~2 的“随机数”,所以这题显然是选择密文攻击,CTF wiki 上有关于 RSA parity oracle 原理的详细介绍,和这一题唯一的区别是 CTF wiki 上的是模 2。

我们可以类比 CTF wiki 上的推理来对这题模三情况的推理,最终的思想是一样的,不断缩小上下界的范围逼近正确值。

upperlower 的初始值分别为 n 和 0,这是明文的范围。还要知道这一题的 n 模 3 得 2。

第 $i$ 次,明文P的范围是:

$$ \frac { x N } { 3 ^ { i } } \leq P < \frac { x N + N } { 3 ^ { i } }.\ \ (\ 1\ ) $$

第 $i+1$ 次,明文P的范围是:

$$ \frac { k N } { 3 ^ { i+1 } } \leq P < \frac { k N + N } { 3 ^ { i+1 } }.\ \ (\ 2\ ) $$

对于不同的返回值(0~2),可以体现出 $k$ 模 3 后的特征(0~2):

$$ \begin{cases} k=3y, &if&\ k\ \equiv 0\ (mod\ 3),y\in N^* \\ k=3y+1, &if&\ k\ \equiv 1\ (mod\ 3),y\in N^* \\ k=3y+2, &if&\ k\ \equiv 2\ (mod\ 3),y\in N^* \end{cases} $$

将不等式(1)分子分母同时乘 3,第 $i$ 次的:

$$ \frac { 3x N } { 3 ^ { i+1 } } \leq P < \frac { 3x N + 3N } { 3 ^ { i+1 } }.\ \ (\ 3\ ) $$

如果返回 0,将 $k = 3y$ 带入 (2) 得:

$$ \frac { 3y N } { 3 ^ { i+1 } } \leq P < \frac { 3y N + N } { 3 ^ { i+1 } }.\ \ (\ 4\ ) $$

由于P一定存在,所以(3)和(4)存在交集,所以 y = x,那么只需要更新上界“upper”:

upper = (2*lower+upper)//3

如果返回 1,将 $k = 3y + 1$ 带入(2),

$$ \frac { 3y N+N } { 3 ^ { i+1 } } \leq P < \frac { 3y N + 2N } { 3 ^ { i+1 } }.\ \ (\ 5\ ) $$

由于P一定存在,所以(3)和(5)存在交集,所以 $y = x$,那么需要同时更新上界和下界:

upper = (lower+2*upper)//3; lower = (2*lower+upper)//3

如果返回 2,将 $k = 3y + 2$ 带入(2),

$$ \frac { 3y N +2N} { 3 ^ { i+1 } } \leq P < \frac { 3y N + 3N } { 3 ^ { i+1 } }.\ \ (\ 6\ ) $$

由于P一定存在,所以(3)和(6)存在交集,所以 $y = x$,那么只需要更新下界“lower”:

lower = (lower+2*upper)//3

这样每一次范围的更新都会缩小范围,最终逼近明文m。

import requests
from Crypto.Util.number import*
PHPSESSID = "jpa80o0gbpi4djabq80iopu7st"
c = 47901621682590941572620529757837523913923282588404656329721569362138054509808822622251355379677887022457532571566654200359453443547599919220729099865254694139150169466016053324444883650312695408132078436223779808465475540169329172223457636008422506025071303750315470905372763770412921709244110136409268083274
n = 0x8f5dc00ef09795a3efbac91d768f0bff31b47190a0792da3b0d7969b1672a6a6ea572c2791fa6d0da489f5a7d743233759e8039086bc3d1b28609f05960bd342d52bffb4ec22b533e1a75713f4952e9075a08286429f31e02dbc4a39e3332d2861fc7bb7acee95251df77c92bd293dac744eca3e6690a7d8aaf855e0807a1157
e = 0x10001
head = {
    'User-Agent': "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36"
}
cookies = {"PHPSESSID": "jpa80o0gbpi4djabq80iopu7st", "public_n": "8f5dc00ef09795a3efbac91d768f0bff31b47190a0792da3b0d7969b1672a6a6ea572c2791fa6d0da489f5a7d743233759e8039086bc3d1b28609f05960bd342d52bffb4ec22b533e1a75713f4952e9075a08286429f31e02dbc4a39e3332d2861fc7bb7acee95251df77c92bd293dac744eca3e6690a7d8aaf855e0807a1157", "public_e": "010001",
           "encrypto_flag": "47901621682590941572620529757837523913923282588404656329721569362138054509808822622251355379677887022457532571566654200359453443547599919220729099865254694139150169466016053324444883650312695408132078436223779808465475540169329172223457636008422506025071303750315470905372763770412921709244110136409268083274"}
r = requests.post("http://106.14.66.189/main.php", cookies=cookies, headers=head)


def getNum(new_c):
    r = requests.post(
        "http://106.14.66.189/abi.php",
        data={'this[is.able': new_c}, cookies=cookies, headers=head)
    print(r.text)
    return int(r.text[7])


upper = n
lower = 0
i = 1
while True:  # n % 3 == 2
    power = pow(3, i, n)
    new_c = (pow(power, e, n) * c) % n  # pow(3^{i}*m, e, n)
    rev = getNum(new_c)
    if rev == 0:  # power * m mod n == 0
        upper = (2 * lower + upper) // 3
    elif rev == 1:  # power * m mod n == (1 or 2)
        temp = upper
        upper = (lower + 2 * upper) // 3
        lower = (2 * lower + temp) // 3
    else:
        lower = (lower + 2 * upper) // 3
    if (upper - lower) < 2:
        break
    i += 1

print(long_to_bytes(upper))

Crypto

modestudy

from Crypto.Util.number import *
from string import digits, ascii_letters
from binascii import unhexlify
from hashlib import sha256
from pwn import *
import os


r = remote("139.224.254.172", "7777")


def proof_of_work():  # 多线程爆破
    rev = r.recvuntil("sha256(")
    suffix = r.recvuntil("+")[:-1].decode()
    rev = r.recvuntil("?=")
    def f(x):
        hashresult = hashlib.sha256(suffix.encode()+x.encode()).digest()
        bits = ''.join(bin(j)[2:].zfill(8) for j in hashresult)
        return bits.startswith('0' * 5)
    prefix = util.iters.mbruteforce(f, digits + ascii_letters, 8, 'upto')
    r.sendline(prefix)


def challenge1():
    r.recvuntil("your choice:")
    r.sendline("1")
    r.recvuntil("session=")
    session = r.recv(16)
    r.recvuntil("checksum=")
    checksum = r.recv(64)
    r.recvuntil("cookie:")
    plain = "session={};admin=0".format(session)
    bit = ((unhexlify(checksum)[15]) ^ ord('0') ^ ord('1'))
    checksum = checksum.decode()
    checksum_final = checksum[:30] + hex(bit)[2:] + checksum[32:]
    newcookie = "session={};admin=1;checksum={}".format(
        session.decode(), checksum_final)
    r.sendline(newcookie)


def challenge2():
    r.recvuntil("your choice:")
    r.sendline("2")
    r.recvuntil("sha256(iv)=")
    sha_iv = r.recv(64).decode()
    r.recvuntil("your choice:")
    r.sendline("1")
    r.sendline("A" * 32)
    r.recvuntil("[+] ")
    plain = r.recvuntil("\n")[:-1]
    m0 = bytes.fromhex(plain[:32].decode())
    m1 = bytes.fromhex(plain[32:].decode())
    iv = long_to_bytes(bytes_to_long(m0) ^ bytes_to_long(m1) ^ bytes_to_long(b"A"*16))
    r.recvuntil("your choice:")
    r.sendline("2")
    assert sha256(iv).hexdigest() == sha_iv
    r.sendline(iv.hex())


def challenge3():
    r.recvuntil("your choice:")
    r.sendline("3")
    r.recvuntil("128bit_ecb_encrypt(cookie):")
    cipher = r.recvuntil("\n")[:-1].decode()
    cipher = bytearray.fromhex(cipher)
    for i in range(16):
        cipher[32 + i] = cipher[64 + i]
    r.sendline(cipher.hex())


def challenge4():
    r.recvuntil("your choice:")
    r.sendline("4")
    r.recvuntil("sha256(secret)=")
    sha_secret = r.recv(64).decode()
    secret = b""
    for Byte in range(16):
        byte_len = (15 - (Byte % 16)) if ((Byte % 16) != 15) else 16
        bound = ((byte_len + Byte + 1) // 16) * 32
        r.recvuntil("your choice:")
        r.sendline("1")
        r.recvuntil("input(encode hex):")
        r_ = os.urandom(byte_len)
        r.sendline(r_.hex())
        r.recvuntil("encrypted msg: ")
        C_ = r.recvuntil("\n")[:-1].decode()
        print("brute force {} byte".format(Byte+1))
        for i in range(256):
            r.recvuntil("your choice:")
            r.sendline("1")
            r.recvuntil("input(encode hex):")
            Pi = int((r_.hex()+secret.hex())[-30:]+long_to_bytes(i).hex(), 16)
            r.sendline(long_to_bytes(Pi).hex())
            r.recvuntil("encrypted msg: ")
            Ci = r.recvuntil("\n")[:-1].decode()
            if Ci[:32] == C_[bound-32:bound]:
                secret += long_to_bytes(i)
                print("Current secret: {}".format(secret))
                break
    r.recvuntil("your choice:")
    r.sendline("2")
    r.recvuntil("secret(encode hex):")
    r.sendline(secret.hex())


def challenge5():
    r.recvuntil("your choice:")
    r.sendline("5")
    r.recvuntil("sha256(secret)=")
    sha_secret = r.recv(64).decode()
    r.recvuntil("(secret).encode(\"hex\")=")
    c = r.recv(32)
    secret = ""
    for i in range(8):
        part_c = c[i*4:i*4+4]
        guess = ""
        for j in range(0xffff):
            temp = "0" * (4 - len(hex(j)[2:])) + hex(j)[2:]
            guess += temp
            if len(guess) == 1024: # 由于向服务端发送接收时间成本较高,所以一次发送1024bits加快爆破速度
                r.recvuntil("your choice:")
                r.sendline("1")
                r.recvuntil("input(encode hex):")
                r.sendline(guess)
                r.recvuntil("encode(\"hex\"):")
                temp_c = r.recv(1024)
                flag = False
                for k in range(256):
                    if temp_c[k*4:k*4+4] == part_c:
                        secret += guess[k*4:k*4+4]
                        flag = True
                        break
                if flag:
                    break
                guess = ""
        print("Current secret:", secret)
    r.recvuntil("your choice:")
    r.sendline("2")
    r.recvuntil("secret(encode hex):")
    r.sendline(secret)


def challenge6():
    r.recvuntil("your choice:")
    r.sendline("6")
    r.recvuntil("iv+aes128_cbc(key,iv,padding(secret)):")
    iv_cbc = r.recvuntil("\n")[:-1].decode()
    iv = bytearray.fromhex(iv_cbc[:32])
    cbc = bytearray.fromhex(iv_cbc[32:64])
    mid = []
    new_iv = bytearray(b'\x00' * 16)
    count = 1
    for i in range(16):
        for j in range(256):
            new_iv[15 - i] = j
            upload = new_iv + cbc
            r.sendline('1')
            r.recvuntil("input your iv+c (encode hex):")
            r.sendline(upload.hex())
            search = r.recvuntil("your choice:")
            if b"success" in search:
                print(search)
                ans = j ^ count
                break
        count += 1
        mid.append(ans)
        for m in range(15 - i, 16):
            new_iv[m] = count ^ mid[15 - m]
    find = ""
    for i in range(16):
        find += hex(iv[i] ^ mid[15 - i])[2:].rjust(2, '0')
    r.sendline('2')
    r.recvuntil("secret(encode hex):")
    r.sendline(find)


proof_of_work()
r.recvuntil("teamtoken=")
r.sendline("icqc487d794f00cdb22409bd5ea7e736")

challenge1()
challenge2()
challenge3()
challenge4()
challenge5()
challenge6()
r.interactive()
# 这边再输入7得到flag
# icqc487d794f00cdb22409bd5ea7e736
# flag{86ac04cc901a04462c55923eedf5affe}