windows10下的unlink逆向分析

前言

实验尝试做windows下的unlink攻击。windows下有safeunlink保护。和glibc一样若free x发生了unlink要求x->fd->bk=x=x->bk->fd
于是想进行一次类似于linux unlink的攻击。中间产生了异常事件。于是对windows的free函数进行了逆向分析

unlink实验

#include "stdafx.h"
#include <Windows.h>
void *pp;
void try1() {
    HANDLE hHeap = HeapCreate(HEAP_GROWABLE, 1024, 10000);


    DWORD zz = 1;

    char *p = (char *)HeapAlloc(hHeap, HEAP_NO_SERIALIZE, 8000);
    bool bRetVal = HeapFree(hHeap, HEAP_NO_SERIALIZE, p);
    HeapAlloc(hHeap, HEAP_NO_SERIALIZE, 528);
    void * ppp = HeapAlloc(hHeap, HEAP_NO_SERIALIZE, 528);
     DWORD z = *(((DWORD *)p) + 0x112) ^ 0xDF0404DF;
    DWORD z1 = *(((DWORD *)p) + 0x113) ^ 0x45;
    pp=HeapAlloc(hHeap, HEAP_NO_SERIALIZE, 528);
    printf("%x", pp);
    HeapAlloc(hHeap, HEAP_NO_SERIALIZE, 528);
    HeapFree(hHeap, HEAP_NO_SERIALIZE, pp);
    *(((DWORD *)p) + 0x112) = 0x41040045 ^ z;
    *(((DWORD *)p) + 0x113) = 0x45 ^ z1;
    *(((DWORD *)p) + 0x114) = DWORD(&pp -0x1);
    *(((DWORD *)p) + 0x115) = DWORD(&pp);
    HeapFree(hHeap, HEAP_NO_SERIALIZE, ppp);
}
int main()
{

    try1();

    printf("%x", pp);
    return 0;
}


在实验过程中heapfree会调用一次raiseexception函数。若为调试器运行则上面的unlink会攻击成功。若无调试器则不会成功。
得出结论这样的unlink攻击不可利用。
于是对windows下rtlfreeheap进行了逆向分析。
经过ida分析得出真正的free过程在rtlpfreeheap函数中。但是此函数被保护不能直接f5。

rtlpfreeheap分析

1.解密heap_entry

这个函数就是windows下的堆free函数。
单步跟踪到此函数对我们free heap的第一个内存操作。

edi寄存器保存的为我们heap结构的地址。
对heap偏移0x4c与0进行比较。
+0x04c EncodeFlagMask 。即判断此堆块是否进行过加密操作。若flag不为0.则进行下面的解密操作。从偏移0x50处取出Encoding密钥异或解密heap_entry头部。
未加密直接跳转至解密后流程。

2.heap_entry检验

mov al,byte ptr ds:[ebx+2]
xor al,byte ptr ds:[ebx+1]
xor al,byte ptr ds:[ebx]
cmp byte ptr ds:[ebx+3],al
heap_entry偏移0,1,2相互异或操作,与偏移3进行对比。可得出偏移3为防止heap_entry被修改的cookie值。
防止off-by-one单字节修改heap_entry.

3.判断上一相邻堆块

通过自己pre_size找到上一堆块位置,计算过程:自己的地址-pre_size<<3

判断前一堆块是否为自己。即自己是否为堆中第一个堆块。
若为自己直接转至第五步

4.判断上一堆块是否free


判断过程:encodeflag>>14 and encode密钥 xor
若heap_entry未被加密 encodeflag>>14=0,0 and 密钥=0。0 xor flag不变。
若heap_entry被加密 encodeflag>>14=1,1 and 密钥=一位密钥。一位密钥 xor flag。flag最低位被解密。
通过test flag最低一位和1进行判断堆块是否free。
free则unlink。否则转第三步

5.判断下一相邻堆块


通过自己size找到下一堆块位置,计算过程:自己的地址+size*8.
解密heap_entry。进行堆块有效性校验(ps:对于上一相邻堆块不进行解密和校验。应该是防止堆溢出)
虽然校验过程与第一步代码不同,但根据单步调试。校验的实际效果是一样的.
heap_entry偏移0,1,2相互异或操作,与偏移3进行对比。

6.判断下一堆块是否free


判断过程同第四步一致。但是若可以进行unlink还要再次对堆块进行校验(unlink前都会进行校验。不论前后)

unlink过程


safe unlink的代码同linux下的保护如出一辙。
要求x->fd->bk=x=x->bk->fd

失败原因

经过一段根据free堆块大小的判断及合并后大小的计算后来到失败处。
其实攻击失败原因很简单。unlink后会再次对相邻堆块进行完整性校验。这里我们的fd指针被修改成我们程序中内存一部分。并不能完成完成性校验。会调用raiseexcition.校验过程和上方一致故不贴出。此步正是unlink内存写入最后一个校验(可惜可惜,如果堆块未启动加密的话伪造heap_entry为0x0000000即可通过校验)。

总结

经过unlink后会进行flag更新,链表更新。将堆块内容填充。最后加密heap_entry等步骤。这里就不继续跟踪了,最后的流程不长。
经过分析可以得知只有在堆块未加密情况下,且能伪造heap_entry为0x0000000才能进行利用。感叹windows的严谨和glibc的粗糙。。。。