Windows pwn 和 Linux pwn 差别还是非常大的,相对而言 Windows pwn 要难一点。
传参
对于64位程序来说,传递的参数不同,导致利用起来略有差异,Windows 的 参数是 rcx, rdx, R8,R9 ,最主要的是 Windows 编译的程序中一般没有 像 Linux 那样好用的 gadget,所以控制参数就比较困难,一般只能直接根据其某些汇编部分来完成一些操作。
函数调用
Linux 的程序一般都是用plt地址的,这样可以方便我们简介用 plt 地址来调用函数,但是 Windows 程序并没有 类似plt地址的结构,而是直接
call [rip + func_iat_offset]
,这样导致我们在 ROP 的时候并不能直接使用该函数,除非知道了 动态库
的基地址,要不然还是很难进行函数调用,但是我们仍然能使用某些汇编部分来完成一些函数操作。
比如下面这样:
call [rip + puts_iat_offset]
mov rsp, rbp
pop rbp
ret
库函数对比
Linux 的 glibc 和一些其他库对于系统资源的开销,特别是对于栈的开销并不是很大,至少很少会出现一个普通的函数调用需要 0x1000
栈内存的情况,但是 Windows
就恰好相反了,如果栈不够大的话,是非常容易栈上溢,光一个 system 函数的调用就需要 0x4000
的栈内存, 而且像 puts
这类的基本函数的栈开销都是超过0x1000
,但是程序的 .data 段仅仅是一个页大小,这样就导致 栈转移 在Windows pwn 中并没有那么好用。
内核
系统调用一般都是由 Windows KERNEL API 来使用的,而且 Windows 的内核比 Linux 大很多,一般都是四五个参数类型的,要想控制那么多参数也并非易事。
调试
Windows pwn 很难恢复成靶机的那种环境,所以对 stack overflow 的漏洞,就很难把握其栈布局来泄露出地址。
这里简述一下我的方法。
Windows 和 Linux 一般动态库以及程序的地址低位是固定,但是栈则不是,我们能利用该区来区分栈地址。
由于Windows 下栈的数据并不可靠,所以较好的方法就是直接泄露 iat 的地址信息来计算基地址。