原题地址:https://adworld.xctf.org.cn/challenges/details?hash=1a1149ba-3b29-11ed-abf3-fa163e4fa609&task_category_id=5

原题代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from Crypto.Util.number import *
from secret import flag
import random
import hashlib
import os

key=os.urandom(8)
def rand(rng):
return rng - random.randrange(rng)
m=[]
random.seed(int(hashlib.md5(key).hexdigest(), 16))
for i in range(len(flag)):
rand(256)
xor=flag[i]^rand(256)
m.append(xor)
print(m)
print(bytes_to_long(key)>>12)

# [140, 96, 112, 178, 38, 180, 158, 240, 179, 202, 251, 138, 188, 185, 23, 67, 163, 22, 150, 18, 143, 212, 93, 87, 209, 139, 92, 252, 55, 137, 6, 231, 105, 12, 65, 59, 223, 25, 179, 101, 19, 215]
# 2669175714787937


题解

观察题目,发现byte_to_long后的key右移12位后得2669175714787937,即0x97B99E652B261,共13位数字,转二进制就是52位,52+12 = 64 bits = 8 bytes,与原题key=os.urandom(8)对应。而key的前52位我们已经知道了,只需要爆破后12位即可。

易得爆破脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from Crypto.Util.number import *
import random
import hashlib
import os

m = [140, 96, 112, 178, 38, 180, 158, 240, 179, 202, 251, 138, 188, 185, 23, 67, 163, 22, 150, 18, 143, 212, 93, 87, 209, 139, 92, 252, 55, 137, 6, 231, 105, 12, 65, 59, 223, 25, 179, 101, 19, 215]

def rand(rng):
return rng - random.randrange(rng)

for j in range(2**12):
tmpkey = long_to_bytes((2669175714787937 << 12) + j)
flag = ""
random.seed(int(hashlib.md5(tmpkey).hexdigest(), 16))
for i in range(len(m)):
rand(256)
xor = m[i]^rand(256)
flag += chr(xor)
if "flag" in flag:
print(flag)
# flag{e319a58c-4dd6-4e6a-a3fb-f4b0d339faba}

这题的考点应该是计算机伪随机数的产生。鉴于题目中用到了较多产生随机数的函数,姑且对这些函数做个不完全的总结。欢迎各路大神在评论区补充。

产生随机数

众所周知,计算机不能产生真正的随机数。在实践中,大多数编程环境会提供一个伪随机数生成器,使得返回值在统计上看起来是随机的。这里不对随机算法进行深入研究,只需知道随机数是由一个名为“种子”的数推算而来。

C的随机数产生

1
2
3
4
5
6
7
8
9
10
11
/* 一个简单的用于产生随机数的程序 */
#include <stdio.h>
#include <stdlib.h>

int main()
{
int a = rand();
printf("%d",a);
return 0;
}

我们可以看到,上面这个程序的核心代码是rand()。它可以随机的产生 0 ~ RAND_MAX 的随机数,RAND_MAX包含在头文件stdlib.h中,默认为32767。
所以我们可以使用a = rand() % (n-m+1) + m;来生成一个m ~ n的随机数。

然而,我们会发现,虽然函数返回值是无法预料的,但是无论运行多少次,这个函数的返回值都是固定的。其根本原因是没有初始化随机种子。种子在每次启动计算机时是随机的,但是一旦计算机启动以后它就不再变化了。也就是说,每次启动计算机以后,种子就是定值了,所以根据公式推算出来的结果(也就是生成的随机数)就是固定的。

以下代码可以帮助我们初始化随机种子:

1
srand((unsigned)time(NULL)); // # include <stdlib.h>  # include <time.h>

原理是使用time(NULL)函数来获取系统时间(从1970年1月1日0点到现在时间的秒数),并转化为unsigned int,最后传给srand()函数作为种子。

Python的随机数产生

os.urandom(n)

返回一个n字节的随机字符串,适用于加密等。

random库

1
import random
  1. random.seed(a)
    设置初始化随机种子,可输出相同随机数序列;a取整数或浮点数,不设置时默认以系统时间为种子。

  2. random.random()
    用于生成一个0.0到1.0的随机数。

  3. random.uniform(a, b)
    生成一个[a, b]之间的随机浮点数(a、b取整数或浮点数)

  4. random.randint(a, b)
    生成一个[a, b]之间随机整数。

  5. random.randrange(start, stop, \[step\])
    生成一个[start, stop]之间以step步数的随机整数;start、stop、step取整数,step不设置时默认值为1。

  6. random.choice(seq)
    从序列类型seq中随机返回一个元素;seq取序列类型,如字符串、列表、元组。

  7. random.shuffle(seq)
    将序列类型中元素随机排序,无返回值,seq被改变(改变原序列),shuffle为洗牌之意;seq取序列类型,如字符串、列表;元组不能被洗牌。

运算符优先级

debug了一小时啊一小时,最后发现tmpkey = long_to_bytes((2669175714787937 << 12) + j)这句左移运算符那里没加括号。
移位的本质不是乘除吗,为什么优先级比加减还要低?
(某乎上有人发了同样的问题)

贴个运算符优先级顺序表在这里以时刻提醒自己。

C:

优先级 运算符(们) 解释
1 () [] -> . 括号/函数调用 索引 间接成员访问 成员访问
2 ++ – + - ! ~ & * sizeof 类型转换 自增(后缀大于前缀) 自减 正 负 非 按位非 取址 解引用 内存大小 类型转换
3 * / % 乘 除 模
4 + - 加 减
5 << >> 左移位 右移位
6 < <= > >= 小于 小于等于 大于 大于等于
7 == != 等于 不等于
8 & 按位与
9 ^ 按位异或
10 | 按位或
11 && 逻辑与
12 || 逻辑或
13 ?: 三目条件运算符
14 = *= /= += -= <<= >= &= |= ^= 各种赋值运算符
15 , 逗号运算符

Python:

优先级 运算符(们) 解释
1 () [] . 括号/函数调用 索引 属性引用
2 **
3 + - ~ 正 负 按位非
4 * / // % 乘 除 整除 模
5 + - 加 减
6 << >> 左移位 右移位
7 & 按位与
8 ^ 按位异或
9 | 按位或
10 < <= > >= 比较运算
11 == != 等于 不等于
12 is not is 实体检查
13 in not in 成员检查
14 not 逻辑非
15 and 逻辑与
16 or 逻辑或