6.1.18 pwn HITBCTF2017 Sentosa

下载文件

题目复现

$ file sentosa
sentosa: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=556ed41f51d01b6a345af2ffc2a135f7f8972a5f, stripped
$ checksec -f sentosa
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FORTIFY Fortified Fortifiable  FILE
Full RELRO      Canary found      NX enabled    PIE enabled     No RPATH   No RUNPATH   Yes     1               3       sentosa
$ strings libc-2.23.so | grep "GNU C"
GNU C Library (Ubuntu GLIBC 2.23-0ubuntu4) stable release version 2.23, by Roland McGrath et al.
Compiled by GNU CC version 5.4.0 20160609.

保护全开,默认开启 ASLR。

在 Ubuntu-16.04 上玩一下:

$ ./sentosa
Welcome to Sentosa Development Center
Choose your action:
1. Start a project
2. View all projects
3. Edit a project
4. Cancel a project
5. Exit
1
Input length of your project name: 10
Input your project name: AAAA
Input your project price: 10
Input your project area: 10
Input your project capacity: 10
Your project is No.0
Welcome to Sentosa Development Center
Choose your action:
1. Start a project
2. View all projects
3. Edit a project
4. Cancel a project
5. Exit
2
Project: AAAA
Price: 10
Area: 10
Capacity: 10
Welcome to Sentosa Development Center
Choose your action:
1. Start a project
2. View all projects
3. Edit a project
4. Cancel a project
5. Exit
3
Not implemented yet
Welcome to Sentosa Development Center
Choose your action:
1. Start a project
2. View all projects
3. Edit a project
4. Cancel a project
5. Exit
4
Input your projects number: 0

可以新增、查看和删除 project,但修改功能还未实现,这似乎意味着我们不能对堆进行修改。

现在我们给 length 输入 0 试试看:

造成了缓冲区溢出,可见字符串读取的函数肯定是存在问题的。

题目解析

下面我们依次来逆向这些函数。

Start a project

通过上面的分析可以得到 project 结构体和 projects 数组:

projects 位于 0x00202040,proj_num 位于 0x002020c0

用户输入的 length 必须小于 0x59,使用 malloc(length+0x15) 分配一块堆空间作为 project,然后调用 read_buf0() 读入 name 到栈上。读入 name 后将其复制到 project 中,然后将 check 置为 1,最后再依次读入 price、area 和 capacity。

程序自己实现的 read_bf0() 函数如下:

正如我们一开始猜测的,这个函数是有问题的,如果输入 0 作为 length,则 length-1(能读入的实际长度) 后得到一个负数,在循环判断时,负数永远不会等于一个正数,于是将读入任意长度的字符串(以结尾),造成缓冲区溢出。

字符串末尾会被加上 \x00,且开启了 Canary,暂时还没想到如何利用,继续往下看。另外特别注意 malloc 后得到的 project 的地址存放在 rsp + 0x6a 的位置。

View all projects

该函数用于打印出所有存在的 project 的信息。

Cancel a project

该函数首先检查 project->check 是否被修改(不等于1),如果没有则释放该 project,并将 projects[i] 置 0。否则程序退出。这个函数似乎没有悬指针之类的问题。

漏洞利用

总结一下,就是在 read_bf0() 函数中存在一个栈溢出漏洞。

我们来看一下 read_bf0() 函数中的内存布局,假设分配一个这样的 project:

所以其实在覆盖到 Canary 之前,我们是有一个 project 地址可以覆盖的,但由于 read_bf0() 会在字符串末尾加 "\x00",所以我们只能够将地址的低位覆盖为 "\x00"。在新增 project 过程的最后,会将 project address 放到数组 projects 中,所以我们可以将覆盖后的 project address 放进数组。然后利用 View 的功能就可以打印出内容。

另外我们应该注意的是上面的 project address 是最后一次 malloc 返回的地址,即最后添加的 project 的 address。在上面的例子中,如果我们将 project address 覆盖掉,则它指向了 project 的 chunk 头。所以我们可以将其指向一个被释放的 fastbin,它的 fd 指针指向了 heap 上的一个地址,只要将其打印出来就可以通过计算得到 heap 基址。

得到了 heap 基址后,我们就可以将 project address 修改为任意的堆地址,从而读取任意信息。所以下一步我们从堆里得到 libc 地址,接着通过 libc 的 __environ 符号得到 stack 地址,最后就可以从栈上得到 Canary。构造 ROP 得到 shell。

leak heap

首先分配 3 个 fast chunk,其中第 2 个利用栈溢出修改 project address,使其指向第 chunk 0。然后依次释放掉 chunk 2 和 chunk 0,此时 chunk 0 的 fd 指向了 chunk 2:

然后 View 打印出来就得到了 heap 基址。这种构造方法还是有一点问题的,不能打印出最高位的 0x55,但我们知道这个值相对固定,所以直接加上就可以了。

leak libc

由于我们不能直接分配一个 small chunk,所以需要构造一个 fake chunk。利用栈溢出修改 project address 可以做到这一点。另外还需要满足 libc free 的检查,还有 Cancel 过程中的 check。

首先分配 5 个 project,其中最后两个利用漏洞修改了 project address,使其指向 fake chunk。此时内存布局如下:

释放掉 chunk 4,此时它将被放进 unsorted bin,其 fd, bk 指针指向 libc:

将它打印出来即可得到 libc 的基址。

leak stack and canary

通过 libc 地址计算出 __environ 的地址,构造 project 并打印出来得到其指向的 stack 地址。然后通过偏移计算得到 canary 地址,同样的方法构造 project,得到 canary。

pwn

最后我们就可以构造 ROP 得到 shell 了。

开启 ASLR。Bingo!!!

exploit

完整的 exp 如下:

参考资料

Last updated

Was this helpful?