原理 只溢出了一个字节,比如循环次数多了1,比如定义a[4],却访问到了a[4],实际应该是a[0]-a[3],虽然只有一字节,但是造成的危害却很大,这里讨论堆上的Off-By-One 利用思路- 溢出字节为可控制任意字节:通过修改大小造成块结构之间出现重叠,从而泄露其他块数据,或是覆盖其他块数据。也可使用 NULL 字节溢出的方法
- 溢出字节为 NULL 字节:在 size 为 0x100 的时候,溢出 NULL 字节可以使得 prev_in_use 位被清,这样前块会被认为是 free 块。(1) 这时可以选择使用 unlink 方法(见 unlink 部分)进行处理。(2) 另外,这时 prev_size 域就会启用,就可以伪造 prev_size ,从而造成块之间发生重叠。此方法的关键在于 unlink 的时候没有检查按照 prev_size 找到的块的大小与prev_size 是否一致。
最新版本代码中,已加入针对 2 中后一种方法的 check ,但是在 2.28 前并没有该 check 。 /* consolidate backward */
if (!prev_inuse(p)) {
prevsize = prev_size (p);
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
/* 后两行代码在最新版本中加入,则 2 的第二种方法无法使用,但是 2.28 及之前都没有问题 */
if (__glibc_unlikely (chunksize(p) != prevsize))
malloc_printerr ("corrupted size vs. prev_size while consolidating");
unlink_chunk (av, p);
}
例子例1int my_gets(char *ptr,int size)
{
int i;
for(i=0;i<=size;i++)
{
ptr[i]=getchar();
}
return i;
}
int main()
{
void *chunk1,*chunk2;
chunk1=malloc(16);
chunk2=malloc(16);
puts("Get Input:");
my_gets(chunk1,16);
return 0;
}
my_gets中存在off-by-one(i<=size应该为i<size) 动态调试: malloc之后:
Off-By-One(未完)
输入17个'a'之后:
Off-By-One(未完)
可以看到溢出到了下一个堆块地presize域 例2int main(void)
{
char buffer[40]="";
void *chunk1;
chunk1=malloc(24);
puts("Get Input");
gets(buffer);
if(strlen(buffer)==24)
{
strcpy(chunk1,buffer);
}
return 0;
}
strlen 函数在计算字符串长度时是不把结束符 '\x00' 计算在内的,但是 strcpy 在复制字符串时会拷贝结束符 '\x00' 。这就导致了我们向 chunk1 中写入了 25 个字节(off-by-one) malloc之后:
Off-By-One(未完)
可以看到大小是0x21,除掉1bit控制字段和0x10的chunk头,只有0x10大小,怎么放的下24B呢,这就是复用了下一个chunk的prevsize域,所以刚刚好 输入'a'*24后:
Off-By-One(未完)
可以看到下个chunk的size字段的低一个字节被改成了0,而这里有很重要的控制字段P(表明前一个chunk是否空闲) 例题例1 Asis CTF 2016 b00ks查看
Off-By-One(未完)
64位程序,除了没开canary之外别的全开,有菜单 idamain函数(已改名):
Off-By-One(未完)
就正常题目流程 get_name:
Off-By-One(未完)
my_read:
Off-By-One(未完)
存在off-by-one,因为循环条退出件是i==a2而不是i==a2-1
Off-By-One(未完)
这样就多了最后一位变0 看看增删改查: creat_book:
Off-By-One(未完)
Off-By-One(未完)
Off-By-One(未完)
流程就是先输入先输入name_size,判断大小后malloc返回到ptr,并存储name进去 然后需要输入内容size,判断大小后malloc返回v5,然后存储内容进去 最后有一个book结构体:
Off-By-One(未完)
存储了name,内容size,内容和id delete_book:
Off-By-One(未完)
各种检查,最后free后也置null了 edit_book:
Off-By-One(未完)
print_book:
Off-By-One(未完)
最后还输出了作者(自己输入的name)的名字 分析在 author name 中需要输入 32 个字节后,我们溢出到了下面的指针域(结构体的后面)置为了'\x00',而之后我们add book的时候又将'\x00'覆盖了,该指针与 author name 直接连接,那么输出作者名字的时候,我们就可以获得堆指针了 p=process('b00ks')
p.recvuntil(':')
p.sendline('a'*32)
p.recvuntil('>')
p.sendline('1')
p.recvuntil('size:')
p.sendline('32')
p.recvuntil('chars):')
p.sendline('lalala')
p.recvuntil('size:')
p.sendline('32')
p.recvuntil('description:')
p.sendline('bibibi')
p.recvuntil('>')
p.sendline('4')
p.recvuntil('a'*32)
#p.recvall()
book1=my_u64(p.recv(6))
success('book1 addr: '+hex(book1))
p.interactive()
Off-By-One(未完)
成功得到 由于程序存在修改作者姓名的功能,所以我们可以修改pointer array 第一个项的低字节。 gdb调试一下: 首先寻找name(输入的地址):
Off-By-One(未完)
我们看到了一个堆上的地址,这个就是leak出的地址 leak的地址:
Off-By-One(未完)
Off-By-One(未完)
第一个存了下标book1,第二个和第三个则分别是
Off-By-One(未完)
之后就是des的size了 如果我们修改的指针落在了des里(size大点),是不是就可以控制了呢 由于des可写,我们在这个地方伪造一个book结构体,可以通过对伪造的结构体的读写,达到任意地址读写了 后面没咋看懂...先放着
|