这次比赛中比较遗憾没有做出来的一道题,赛后看了其他大佬的writeup才明白了怎么做这道题,也记录下来,个人觉得还是比较有质量的一道题了,虽然大佬说是简单题。关于这一点,想到了一个算是很恰当的比喻,就像是做高考题,总有学霸说“这道题一看就知道怎么做了啊”,但是自己在做的时候就是百思不得其解,到最后看了答案又恍然大悟:好像这种解法以前做题就遇到过,虽然当时不会,现在还是不会。这么来看,我好像把这个博客当成错题集来用了。

这道题很简单,就两个操作,1)自定义填充flag,2)获取rsa加密后的flag。后一步操作的N和e都是固定的,没什么好说,主要是填充的操作有漏洞。

首先,flag长度固定为38字节,而用于加密的flag字符串是256字节。填充分为两部分,用户填充的user_pad和代码填充的code_pad,code_pad的过程如下(其中的参数s已经经过user_pad了):

  1. def pad(s):
  2. s += (256 - len(s)) * chr(256 - len(s))
  3. ret = ['\x00' for _ in range(256)]
  4. for index, pos in enumerate(s_box):
  5. ret[pos] = s[index]
  6. return ''.join(ret)

其中的s_box是AES的S盒,完全照搬,但实际上的作用是重排。过程就是,在s后面填充字符chr(256-len(s))以保证长度为256,再经过重排输出。填充的字符之所以那么奇怪,是为了之后恢复时能够去掉这些填充位:

  1. def unpad(s):
  2. ret = ['\x00' for _ in range(256)]
  3. for index, pos in enumerate(invs_box):
  4. ret[pos] = s[index]
  5. return ''.join(ret[0:-ord(ret[-1])])

最后一行,通过ret[-1]就能够知道原来的长度到哪里,就能够截断并输出不带code_pad的字符串,本来是个很聪明的做法,但问题也恰恰出现在这里。根据pad函数,如果

一道与普通rsa不太相同的题,基本没见过这个思路,但实际上并不难,需要一点数学的直觉。

直接看代码可以知道,所使用的消息m、大整数N和加密指数e都是固定的,但需要用户自行输入一个padding,加密过程是:

cipher=(m+sha256(padding))e mod N

其中e的值固定为3,但因为消息m过长,三次方后超过N,导致直接开立方不奏效。由于密钥参数都是确定的,因此可以用两次加密作差来解决求模的问题,记sha256(padding)p,密文为c,那么两次加密的结果记做:

c1=(m+p1)3 mod N
c2=(m+p2)3 mod N

直接相减可以消去m的立方项,由于pm