欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

nullconCTF/Crypto/RockPaperScissors

程序员文章站 2022-06-13 11:38:21
...

2020年2月8号的nullconCTF Online的一道Crypto题

拿到题目,打开rps.py是源码, 然后又给了nc的地址, 直接nc进去看给出的是一段文字(这里由于结束后官方关闭了端口,后面无法进行nc), 直接看源码, 不难发现,这是一道代码审计题, 题目给出的RockPaperScissors是石头剪子布. 下面我们一个函数一个函数的来看.

def gen_commitments():
    secret = bytearray(Random.get_random_bytes(16))
    rc = hash(secret + b"r")
    pc = hash(secret + b"p")
    sc = hash(secret + b"s")
    secret = hex(bytes_to_int(secret))[2:]
    rps = [("r", rc), ("p", pc), ("s", sc)]
    random.shuffle(rps)
    return secret, rps

先给出这个函数, rc, pc, sc 就是经过hash加密后的三个数, 不难发现, 每一个数都是经过secret加上一个字符加密后得到, 最后转换成hex输出, 并打乱顺序, 这就是nc中所显示的内容, 然后他说了The fisrt one is my move, 所以这时候就需要我们去找出他到底出了什么. 然后我们用下面这段逻辑去赢他,总共赢20轮,这就是这道题的大体思路了.

def check_win(move, inp):
    if move == "r":
        if inp == "p":
            return True
        else:
            return False
    elif move == "s":
        if inp == "r":
            return True
        else:
            return False
    elif move == "p":
        if inp == "s":
            return True
        else:
            return False
    return False

进入主题, 这道题最重要的就是对下面这段函数的分析,就是对他的加密过程的分析.

def hash(secret):
    state = bytearray([208, 151, 71, 15, 101, 206, 50, 225, 223, 14, 14, 106, 22, 40, 20, 2])
    secret = pad(secret, 16)
    secret = group(secret)
    for roundkey in secret:
        for _ in range(round):
            state = repeated_xor(state, roundkey)
            for i in range(len(state)):
                state[i] = sbox[state[i]]
            temp = bytearray(16)
            for i in range(len(state)):
                temp[p[i]] = state[i]
            state = temp
    return hex(bytes_to_int(state))[2:]

先从

for i in range(len(state)):
	state[i] = sbox[stat[i]]

开始, 这里: state的值是sbox的下标, 并将state的值为下标的sbox对应的值给到state,

sbox = [221, 229, 120, 8, 119, 143, 33, 79, 22, 93, 239, 118, 130, 12, 63, 207, 90, 240, 199, 20, 181, 4, 139, 98, 78, 32, 94, 108, 100, 223, 1, 173, 220, 238, 217, 152, 62, 121, 117, 132, 2, 55, 125, 6, 34, 201, 254, 0, 228, 48, 250, 193, 147, 248, 89, 127, 174, 210, 57, 38, 216, 225, 43, 15, 142, 66, 70, 177, 237, 169, 67, 192, 30, 236, 131, 158, 136, 159, 9, 148, 103, 179, 141, 11, 46, 234, 36, 18, 191, 52, 231, 23, 88, 145, 101, 17, 74, 44, 122, 75, 235, 175, 54, 40, 27, 109, 73, 202, 129, 215, 83, 186, 7, 163, 29, 115, 243, 13, 105, 184, 68, 124, 189, 39, 140, 138, 165, 219, 161, 150, 59, 233, 208, 226, 176, 144, 113, 146, 19, 224, 111, 126, 222, 178, 47, 252, 99, 87, 134, 249, 69, 198, 164, 203, 194, 170, 26, 137, 204, 157, 180, 168, 162, 56, 81, 253, 213, 45, 21, 58, 24, 171, 37, 82, 53, 50, 84, 196, 232, 242, 244, 64, 80, 10, 114, 212, 187, 205, 28, 51, 182, 16, 107, 245, 211, 85, 92, 195, 5, 197, 200, 31, 183, 61, 123, 86, 167, 154, 41, 151, 35, 247, 246, 153, 95, 206, 149, 76, 112, 71, 230, 106, 188, 172, 241, 72, 156, 49, 14, 214, 155, 110, 102, 116, 128, 160, 135, 104, 77, 91, 190, 60, 42, 185, 96, 97, 251, 218, 133, 209, 65, 227, 3, 166, 255, 25]

经过测试,发现sbox为0-255, 并且无重复不修改, 这样子, 每个值对应的下标就是固定的, 所以我们就可以通过state的值去匹配sbox的下标,并将下标给到state,通过这样子来恢复原有的state.

for i in range(16):   //16是state的长度
		for j in range(256):   //256是sbox的长度
			if state[i] == sbox[j]:
				state[i] = j
 				break

nullconCTF/Crypto/RockPaperScissors
nullconCTF/Crypto/RockPaperScissors

 p = [5, 9, 1, 8, 3, 11, 0, 12, 7, 4, 14, 13, 10, 15, 6, 2]
 temp = bytearray(16)
 for i in range(len(state)):
      temp[p[i]] = state[i]
 state = temp

这里的temp = bytearray(16)是先占用了16个空字节, 可以不用管, 这里的逻辑跟上面差不多, p是0-15,并且无重复不修改. 这里的state是从下标0-15的值依次给到temp对应p的值为下标的位置, 例如state[0]的值给到temp[p[0=5] 最后再将state更新为temp, 所以定义一个temp = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] len(temp) = 16

只需要将state[p[0] = 5] 的值给回temp[0] 即可, 最后更新state = temp

nullconCTF/Crypto/RockPaperScissors
nullconCTF/Crypto/RockPaperScissors

def repeated_xor(p, k):
    return bytearray([p[i] ^ k[i % len(k)] for i in range(len(p))])
state = repeated_xor(state, roundkey)

这里通过函数名不难发现是异或, state和roundkey异或最后的值再给到state, 然后发现roundkey是在secret中的, 并且secret = group(secret)

def group(input, size = 16):
    return [input[i * size: (i + 1) * size] for i in range(len(input) // size)]

这个地方是最重点的地方, 先随机生成一个16字节的secret, 加上"r"

nullconCTF/Crypto/RockPaperScissors

定义题目中的pad函数, 然后调用, 将值给到secret

nullconCTF/Crypto/RockPaperScissors

发现pad函数的作用是在后面填充了一堆0xf, 再调用题目中的group函数

nullconCTF/Crypto/RockPaperScissors

group函数是将secret分成两部分然后再对每个部分进行加密, 所以在

for roundkey in secret中, roundkey只有两个, 第一个是secret, 第二个是r,p,s中的其中一个然后在后面加上填充字符.

所以

1: roundkey2只有三个值:
      rkey = bytearray(b'r\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f')     
      pkey = bytearray(b'p\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f')
      skey = bytearray(b's\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f')
 2: secret+r/p/s 不论加上哪一个, 其实都是单独加密的, 由于sbox, state, 和p都是固定, 
     每一轮, secret的加密结果都是一样的

由于加密的时候先对secret加密,再对roundkey2加密,所以反过来, 解密的时候要先对roundkey2进行解密, 解出来的结果就是roundkey1也就是secret的加密后的结果

这样就可以通过列举, 在给出的三个数据中, 对每一个数据单独进行r p s解密后,对其结果进行比较, 只有当三个数据解出来的结果一样时, 才能确定r p s的顺序.

nullconCTF/Crypto/RockPaperScissors

如上图res(s1, skey) == res(s2, rkey) == res(s3, pkey) ,确定顺序为s r p

ps: 有一点值得注意, 就是有时侯给出的数据会是31位的, 这样运行的时候会出现Odd String, 这时候只需要在该数据的最前面填充0即可.

def check_len(state):
	if len(state) != 32:
		return '0' + state
	return state

给出最终的exp.py

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

sbox = [221, 229, 120, 8, 119, 143, 33, 79, 22, 93, 239, 118, 130, 12, 63, 207, 90, 240, 199, 20, 181, 4, 139, 98, 78, 32, 94, 108, 100, 223, 1, 173, 220, 238, 217, 152, 62, 121, 117, 132, 2, 55, 125, 6, 34, 201, 254, 0, 228, 48, 250, 193, 147, 248, 89, 127, 174, 210, 57, 38, 216, 225, 43, 15, 142, 66, 70, 177, 237, 169, 67, 192, 30, 236, 131, 158, 136, 159, 9, 148, 103, 179, 141, 11, 46, 234, 36, 18, 191, 52, 231, 23, 88, 145, 101, 17, 74, 44, 122, 75, 235, 175, 54, 40, 27, 109, 73, 202, 129, 215, 83, 186, 7, 163, 29, 115, 243, 13, 105, 184, 68, 124, 189, 39, 140, 138, 165, 219, 161, 150, 59, 233, 208, 226, 176, 144, 113, 146, 19, 224, 111, 126, 222, 178, 47, 252, 99, 87, 134, 249, 69, 198, 164, 203, 194, 170, 26, 137, 204, 157, 180, 168, 162, 56, 81, 253, 213, 45, 21, 58, 24, 171, 37, 82, 53, 50, 84, 196, 232, 242, 244, 64, 80, 10, 114, 212, 187, 205, 28, 51, 182, 16, 107, 245, 211, 85, 92, 195, 5, 197, 200, 31, 183, 61, 123, 86, 167, 154, 41, 151, 35, 247, 246, 153, 95, 206, 149, 76, 112, 71, 230, 106, 188, 172, 241, 72, 156, 49, 14, 214, 155, 110, 102, 116, 128, 160, 135, 104, 77, 91, 190, 60, 42, 185, 96, 97, 251, 218, 133, 209, 65, 227, 3, 166, 255, 25]
p = [5, 9, 1, 8, 3, 11, 0, 12, 7, 4, 14, 13, 10, 15, 6, 2]
round = 16

rkey = bytearray(b'r\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f')
pkey = bytearray(b'p\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f')
skey = bytearray(b's\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f')

def check_len(state):
	if len(state) != 32:
		return '0' + state
	return state

def repeated_xor(p, k):
    return bytearray([p[i] ^ k[i % len(k)] for i in range(len(p))])

def byte_decode_hex(state):
	return bytearray(state.decode('hex'))

def res(state, roundkey):
	state = check_len(state)
	state = byte_decode_hex(state)
	for _ in range(round):
		temp = bytearray(16)
		for i in range(len(state)):
			temp[i] = state[p[i]]
		state = temp
		for i in range(16):
			for j in range(256):
				if state[i] == sbox[j]:
					state[i] = j
	 				break
		state = repeated_xor(state, roundkey)
	return state

conn = remote("crypto1.ctf.nullcon.net", "5000")
conn.recvuntil("is my move: ")
a = conn.recvline()
state = a.split()

print state[0]
print state[1]
print state[2]

count = 1

while(count <= 20) {
	conn.recvuntil("is my move: ")
	state = conn.recvline().split()
	# r s p
	if res(state[0], rkey) == res(state[1], skey) and res(state[0], rkey) == res(state[2], pkey):
		print("r")
		conn.sendline("p")
	# r p s
	elif res(state[0], rkey) == res(state[1], pkey) and res(state[0], rkey) == res(state[2], skey):
		print("r")
		conn.sendline("p")
	# s p r
	elif res(state[0], skey) == res(state[1], pkey) and res(state[0], skey) == res(state[2], rkey):
		print("s")
		conn.sendline("r")
	# s r p
	elif res(state[0], skey) == res(state[1], rkey) and res(state[0], skey) == res(state[2], pkey):
		print("s")
		conn.sendline("r")
	# p r s
	elif res(state[0], pkey) == res(state[1], rkey) and res(state[0], pkey) == res(state[2], skey):
		print("p")
		conn.sendline("s")
	# p s r
	elif res(state[0], pkey) == res(state[1], skey) and res(state[0], pkey) == res(state[2], rkey):
		print("p")
		conn.sendline("s")
	count += 1	
}

if count=21:
	conn.recvuntil("Your reward is")
	a = conn.recvline()
	print a


conn.interactive()

附上题目源码:

#!/usr/bin/env python3
from Crypto import Random
from Crypto.Random import random
from Crypto.Util.number import *
from secret import flag

sbox = [221, 229, 120, 8, 119, 143, 33, 79, 22, 93, 239, 118, 130, 12, 63, 207, 90, 240, 199, 20, 181, 4, 139, 98, 78, 32, 94, 108, 100, 223, 1, 173, 220, 238, 217, 152, 62, 121, 117, 132, 2, 55, 125, 6, 34, 201, 254, 0, 228, 48, 250, 193, 147, 248, 89, 127, 174, 210, 57, 38, 216, 225, 43, 15, 142, 66, 70, 177, 237, 169, 67, 192, 30, 236, 131, 158, 136, 159, 9, 148, 103, 179, 141, 11, 46, 234, 36, 18, 191, 52, 231, 23, 88, 145, 101, 17, 74, 44, 122, 75, 235, 175, 54, 40, 27, 109, 73, 202, 129, 215, 83, 186, 7, 163, 29, 115, 243, 13, 105, 184, 68, 124, 189, 39, 140, 138, 165, 219, 161, 150, 59, 233, 208, 226, 176, 144, 113, 146, 19, 224, 111, 126, 222, 178, 47, 252, 99, 87, 134, 249, 69, 198, 164, 203, 194, 170, 26, 137, 204, 157, 180, 168, 162, 56, 81, 253, 213, 45, 21, 58, 24, 171, 37, 82, 53, 50, 84, 196, 232, 242, 244, 64, 80, 10, 114, 212, 187, 205, 28, 51, 182, 16, 107, 245, 211, 85, 92, 195, 5, 197, 200, 31, 183, 61, 123, 86, 167, 154, 41, 151, 35, 247, 246, 153, 95, 206, 149, 76, 112, 71, 230, 106, 188, 172, 241, 72, 156, 49, 14, 214, 155, 110, 102, 116, 128, 160, 135, 104, 77, 91, 190, 60, 42, 185, 96, 97, 251, 218, 133, 209, 65, 227, 3, 166, 255, 25]
p = [5, 9, 1, 8, 3, 11, 0, 12, 7, 4, 14, 13, 10, 15, 6, 2]
round = 16


def pad(data, size = 16):
    pad_byte = (size - len(data) % size) % size
    data = data + bytearray([pad_byte]) * pad_byte
    return data


def repeated_xor(p, k):
    return bytearray([p[i] ^ k[i % len(k)] for i in range(len(p))])


def bytes_to_int(xbytes):
    return bytes_to_long(xbytes)


def int_to_bytes(x):
    return long_to_bytes(x, 16)


def group(input, size = 16):
    return [input[i * size: (i + 1) * size] for i in range(len(input) // size)]


def hash(secret):
    state = bytearray([208, 151, 71, 15, 101, 206, 50, 225, 223, 14, 14, 106, 22, 40, 20, 2])
    secret = pad(secret, 16)
    secret = group(secret)
    for roundkey in secret:
        for _ in range(round):
            state = repeated_xor(state, roundkey)
            for i in range(len(state)):
                state[i] = sbox[state[i]]
            temp = bytearray(16)
            for i in range(len(state)):
                temp[p[i]] = state[i]
            state = temp
    return hex(bytes_to_int(state))[2:]

def gen_commitments():
    secret = bytearray(Random.get_random_bytes(16))
    rc = hash(secret + b"r")
    pc = hash(secret + b"p")
    sc = hash(secret + b"s")
    secret = hex(bytes_to_int(secret))[2:]
    rps = [("r", rc), ("p", pc), ("s", sc)]
    random.shuffle(rps)
    return secret, rps

def check_win(move, inp):
    if move == "r":
        if inp == "p":
            return True
        else:
            return False
    elif move == "s":
        if inp == "r":
            return True
        else:
            return False
    elif move == "p":
        if inp == "s":
            return True
        else:
            return False
    return False

def main():
    print("Beat me in Rock Paper Scissors 20 consecutive times to get the flag")
    for i in range(20):
        secret, rps = gen_commitments()
        move = rps[0][0]
        print("Here are the possible commitments, the first one is my move:", " ".join(map(lambda s: s[1], rps)))
        inp = input("Your move:")
        res = check_win(move, inp)
        print("My move was:", move, "Secret was:", secret)
        if not res:
            print("You lose!")
            exit(0)

    print("You win")
    print("Your reward is", flag)
    exit(0)

if __name__ == '__main__':
    main()
相关标签: hash