windows pwn初探
前言
这几天跟着xxrw和lyyl学到了不少winpwn的知识,整理一下
环境搭建
checksec工具:
调试工具:
windbg previewer
调试
win_server监听某个程序,虚拟机中remote连接,raw_input()等待windbg attach之后再发送数据。
常用命令:
- bp [exe_name]+offset:断在offset处
- bp pe_base+offset:断在offset处
- lm:查看加载的dll及pe地址空间
- u addr:查看addr处的代码
- g:运行到断点
- p:单步步过
- t:单步步入
- !address [target_addr]:查看target_addr所属的地址范围
- dps [addr]:查看addr处开始的一段范围内的值,并且搜索出二进制对应的符号
- s -d 0x0 l?0x7fffffff 0x12345678 全局搜索0x12345678
寻找gadget:
ropper --file ./ntdll.dll --nocolor > gadget
注意因为linux/win的汇编不同不可使用ROPGadget找gadget。
基础知识
- GS:类似canary
- ASLR:地址随机化,但是只有开机的时候才会随机一次
windows异常处理机制
scopeTable结构体中保存了try块相匹配的except,__finally的值,在main函数开始的入口就被压入到栈中。
在遇到异常时,先执行except_handler4函数,该函数首先将scope_table的地址同security_cookie异或得到实际地址,之后验证gs的值,满足要求后当try_level=0xfffffffe(-2)时,调用scope_table中的filter_func。
HitbGsec-babystack
程序逻辑 && 漏洞利用
通过checksec可以看到SafeSEH关闭,ASLR开启,有GS保护。
程序开始会leak出main_addr和stack_addr,根据前者可以得到pe_base,后面任意地址读可以借此得到security_cookie的值(或者先leak gs再用gs异或ebp)。
程序最后有个后门,a,b被初始化为1,当a+b=3时触发后门,由于溢出不到a,b,因此后门实际触发不到,我们通过伪造scope_table来实现。
1 | int __cdecl __noreturn main(int argc, const char **argv, const char **envp) |
观察一下函数入口处,push了try_level,其值为-2,然后push了seh_scope,__except_handler4
异常处理指针,push了fs:[0]
,也就是next指针,再往后push了ms_exc.exc_ptr和old_esp。
ipad画了个自己能看懂的图标记了一下。可以将它想象成把一个大的结构体push到了栈上。
1 | 00000000 CPPEH_RECORD struc ; (sizeof=0x18, align=0x4, copyof_9) |
seh_scope的结构如下,伪造的时候前面照抄,后面将两个函数指针改成后门地址。最后触发0地址访问错误进行异常函数调用。
1 | .rdata:00403688 init_seh_secope_table dd 0FFFFFFE4h ; GSCookieOffset |
1 | xt:004010B0 ; __unwind { // __except_handler4 |
exp.py
1 | # encoding=utf-8 |
qwb2020-easyoverflow
程序逻辑 && 漏洞利用
程序没有开CFG,有栈溢出,可以leak三次数据。
由于win的地址随机化的原因,我们先leak出pe_base/ntdll_base,然后leak gs,利用栈溢出的rop将security_cookie泄露出来进而异或得到rsp栈地址,注意此时pop rbx=1可以重复进行rop,再二次溢出根据read@iat得到ucrtdll_base,第三次溢出执行system(“cmd.exe”)。
win x64的前四个参数寄存器为rcx rdx r8 r9。
注意后面溢出的时候rsp值改变,相应的gs值也要修改为new_rsp ^ security_cookie。
1 | __int64 main_func() |
exp.py
1 | # encoding=utf-8 |
SCTF2020-EasyWinHeap
程序逻辑 & 漏洞利用
堆菜单题,有溢出和UAF。bss上先分配了一大块堆地址用于存放chunks,先UAF+Show leak出heap_addr,进而unlink达到任意地址写的效果。
这里多提一下win的unlink,在AngelBoy的Slide里有提到win的unlink更加简单,因为flink和blink指向的是data_ptr,因此只需要伪造fd = &p-8,bk= &p
即可。另外为了绕过检查我们需要先释放chunk1 chunk3,再编辑chunk1,这样是为了绕过list_head的检查。另外很有意思的是在后面的一个check失败的情况下程序也不会abort,而是阻止新chunk插入链表,详情可以看ppt。之后再释放chunk0即可触发unlink,地址任意写,将chunk_addr改成heap_addr来leak上面的pe_addr,最后在iat表泄露出urctbase,通过s -a 0 L?80000000/2 "cmd.exe"
搜索cmd字符串,通过u ucrtbase!system
查看system函数地址,最后任意地址写将函数指针改为system,布置cmd到堆上。
1 | int main_func() |
exp.py
1 | # encoding=utf-8 |
参考资料
非常感谢xxrw和lyyl两位大佬的指导 :)