DASCTF May × BJDCTF 3rd 安恒五月赛

MISC

Questionaire

叮~ 您有一份调查问卷~ 请查收~ https://forms.gle/Vmzt99LazrtXsRLM9

谷歌问卷调查,FQ打开,北京知识,每回答对一个空得flag片段。

直接F12看源码大法,有答案,有结果,拼接得flag。

SOJZixI9wepd6Ns

/bin/cat 2

[CAUTION] cats as numerous as stars are coming… 解出的答案经md5后提交~

远看可现一张二维码,没用脚本,直接stegesolve.jar,取Red plane得明显的黑白图片,上扫码工具得flag。

testyournc

Test your nc

hint1: /f1a9.bak 你看到了嘛?

hint2: 用df命令看看硬盘总共多大,再看看flag多大。

nc交互题。

ls发现目录下有flag和readme,cat flag发现打开卡死,ls -al查看发现flag文件很大(15T+)。

在根目录找到可读文件f1a9.bak,是生成flag文件的源码:

a21b0f21198665bdd14dde435f06a3c

发现是每个字符都随机偏移 $1024\times1024\times1024\times k\quad (\rm offset\mit \leq k\leq \rm offset+2048 )$ 后写入,$\rm offset$ 本身也在不断递增,中间填满空字符,无法按固定位置读取。

试过很多shell命令的结合,cat,tr,sed,grep,hexdump都各种花样卡死…

受大佬提示此类文件是稀疏文件,非空数据块存入磁盘,空数据库不占用磁盘空间。

尝试将文件移到本地处理,使用tar命令将flag文件打包到某个可写位置,比如/var/tmp,注意到tar有个参数可高效处理稀疏文件:

-S或—sparse 倘若一个文件内含大量的连续0字节,则将此文件存成稀疏文件。

上命令:tar -zcvfS /var/tmp/flag.tar.gz flag

这时查看flag.tar.gz文件大小就很小了(0.3KB+)

在nc环境运行

cat flag.tar.gz | base64

再在本地运行

echo -n "H4sIAAAAAAAAA+3RTUrDQBgG4AgK/oBnqDfImCbpUty78gRptQURAlpXIvQIXkDEK3gBPZJHMK2iRasbGWfzPBBm5pu/N8n4vJlkkeWdqp8v2s68DXX5Pg7zJ2ShqKpQ1aHO6yzf75YXWe84drC5q8tpc9HrZRdtO/1t3Wg6jhMg//TeD4t++KE+W+xae5h97F2urz8dfKmvvU3sHnXt1rfb3yY37rKVp20+rr5l+/B5ZX3ncvU5i8s/37T738uhlr8A/+vl/naWOgMAAAAQ1/i8mVynDgEAAABEVVb9qqiLInUOAAAAIJ5BHU5TZwAAAADi6udFkzoDAAAAENegnzoBAAAAENvJMHUCAAAAILaiLJrRKB8My3L/JnUYAAAAIIpJ2570ztrhXuogAAAAAAAAwJ+9AsKEhXoAoAAA" | base64 -d

把得到的base64字符串存储到本地。

本地解压出原始flag文件(15T+):

tar -zxvfS flag.tar.gz

再使用

tar -cvfS flag-new flag

得到稀疏处理过的flag稀疏文件。

6f7399887fccfd2707d62fe043a209d

PS:写到最后发现,不用在本地处理,直接 tar -cvfS /var/tmp/flag.tar.gz flag 应该也成。

CRYPTO

bbcrypto

its so simple

给定脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# -*- coding:utf-8 -*-
import A,SALT
from itertools import *

def encrypt(m, a, si):
c=""
for i in range(len(m)):
c+=hex(((ord(m[i])) * a + ord(next(si))) % 128)[2:].zfill(2)
return c

if __name__ == "__main__":
m = 'flag{********************************}'
a = A
salt = SALT
assert(len(salt)==3)
assert(salt.isalpha())
si = cycle(salt.lower())
print("明文内容为:")
print(m)
print("加密后的密文为:")
c=encrypt(m, a, si)
print(c)

#加密后的密文为:
#177401504b0125272c122743171e2c250a602e3a7c206e014a012703273a3c0160173a73753d

salt长度为3,又知明文m的前5位和后1位字符,有对应关系:

('f'*a+salt[0])%128 = 0x17

('l'*a+salt[1])%128 = 0x74

('a'*a+salt[2])%128 = 0x01

('g'*a+salt[0])%128 = 0x50

('{'*a+salt[1])%128 = 0x4b

('}'*a+salt[1])%128 = 0x3d

可用脚本爆破:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import string
lower = string.printable[10:36]
for a in range(1001):
for s0 in lower:
if hex((102*a+ord(s0)) % 128) == '0x17' and hex((103*a+ord(s0)) % 128) == '0x50':
print(a, s0)

for a in range(1001):
for s1 in lower:
if hex((108*a+ord(s1)) % 128) == '0x74' and hex((123*a+ord(s1)) % 128) == '0x4b' and hex((125*a+ord(s1)) % 128) == '0x3d':
print(a, s1)

for a in range(1001):
for s2 in lower:
if hex((97*a+ord(s2)) % 128) == '0x1':
print(a, s2)

容易得到符合条件的最小a=57及对应salt='ahh'

在用脚本逆向求出flag{}中间字符:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
a = 57
si0 = 97
si1 = 104
si2 = 104
c = [0x17, 0x74, 0x01, 0x50, 0x4b, 0x01, 0x25, 0x27, 0x2c, 0x12, 0x27, 0x43, 0x17, 0x1e, 0x2c, 0x25, 0x0a, 0x60, 0x2e, 0x3a, 0x7c, 0x20, 0x6e, 0x01, 0x4a, 0x01, 0x27, 0x03, 0x27, 0x3a, 0x3c, 0x01, 0x60, 0x17, 0x3a, 0x73, 0x75, 0x3d]

flag=''
for i in range(len(c)):
if i%3 ==0:
for m in range(0,127):
if (m*57+si0)%128 == c[i]:
flag+=chr(m)
else:
for m in range(0,127):
if (m*57+si1)%128 == c[i]:
flag+=chr(m)
print(flag)

得到flag~

easyLCG

easy LCG

线性同余生成器(LCG)

线性同余生成器是个产生伪随机数的方法,它是根据递归公式:

$N_{j+1}=(A\times N_j+B)\pmod M$

其中$A,B,M$是生成器设定的常数,$A$为乘数,$B$为增量,$M$为模数。

已知初始$seed_0$在生成器运行一次得到:

$seed_1=(a\times seed_0+b) \% m$

$state_1=seed_1\gt\gt16$,即 $seed_1=(state_1\lt\lt16)+k_1$

同样:

$seed_2=(a\times seed_1+b) \% m$

$state_2=seed_2\gt\gt16$,即 $seed_2=(state_2\lt\lt16)+k_2$

联立有:

$state_2=(a \times ((state_1 \lt\lt 16)+ k_1 ) + b) \% m \gt\gt16 $

已知 $a$,$b$,$m$,$state_1$,$state_2$,可以爆破得$k_1$:

1
2
3
4
5
6
7
8
9
10
11
12
a = 3844066521
b = 3316005024
m = 2249804527
state1 = 16269
state2 = 4249

for k in range(2**32):
seed1 = (state1<<16)+k
res = ((a*seed1+b)%m)>>16
if res==state2:
print(seed1)
break

而后续的gen_AB()是在以上面迭代两次后的$seed_2$为初始seed的,根据得到的$seed_1$计算$seed_2=278490266$,带入后续步骤即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
from Crypto.Util.number import *
a = 3844066521
b = 3316005024
m = 2249804527
state1 = 16269
state2 = 4249

class LCG:
def __init__(self):
self.a = 3844066521
self.b = 3316005024
self.m = 2249804527
self.seed = 278490266

def next(self):
self.seed = (self.a*self.seed+self.b) % self.m
return self.seed >> 16

class DH:
def __init__(self):
self.lcg = LCG()
self.g = 183096451267674849541594370111199688704
self.m = 102752586316294557951738800745394456033378966059875498971396396583576430992701
self.A, self.a = self.gen_AB()
self.B, self.b = self.gen_AB()
self.key = pow(self.A, self.b, self.m)

def gen_AB(self):
x = ''
for _ in range(64):
x += '1' if self.lcg.next() % 2 else '0'
return pow(self.g, int(x, 2), self.m), int(x, 2)

Cipher = 13040004482819935755130996285494678592830702618071750116744173145400949521388647864913527703
DH = DH()
print("flag = {}".format(long_to_bytes(Cipher ^ DH.key)))

PWN

TaQiniOj-0

nc交互题。

用C语言写语句,读取flag.txt的内容。

测试发现禁用了关键字home|ctf|flag,直接字符串分割绕过:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<stdio.h>
#include<string.h>
void main() {
FILE *fp;
char str[100];
char path[50] ="/ho";
char path2[50]="me/ct";
char path3[50]="f/fl";
char path4[50]="ag";
strcat(path3,path4);
strcat(path2,path3);
strcat(path,path2);
fp = fopen(path, "r");
fgets(str,100,fp);
printf("%s\n",str);
}