VC下提前注入进程的一些方法3——修改程序入口点
2013-03-15 14:43
323 查看
前两节中介绍了通过远线程进行注入的方法。现在换一种方法——修改进程入口点。(转载请指明出处)
在PE文件中,其中有个字段标识程序入口点位置。我们通过这个字段,到达程序入口点。PE文件的结构我这儿不讨论(我会在之后写关于PE文件的介绍和研究),我只列出一些和程序入口点有关的数据结构
[cpp] view
plaincopy
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
[cpp] view
plaincopy
typedef struct _IMAGE_OPTIONAL_HEADER {
//
// Standard fields.
//
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
DWORD BaseOfData;
//
// NT additional fields.
//
DWORD ImageBase;
……
}
其中ImageBase是程序加载的基址,AddressOfEntryPoint是代码执行的入口偏移。于是我们的程序入口点是
[cpp] view
plaincopy
PIMAGE_DOS_HEADER lpstDosHeader = (PIMAGE_DOS_HEADER)(LPSTR)lpMapFile;
PIMAGE_NT_HEADERS lpstNtHeaders = (PIMAGE_NT_HEADERS)((LPSTR)lpMapFile + lpstDosHeader->e_lfanew );
dwPEEntry = lpstNtHeaders->OptionalHeader.AddressOfEntryPoint + lpstNtHeaders->OptionalHeader.ImageBase;
我们将从程序入口点开始搜索Call这个指令。这个地方存在一个经验,就是一般(如果没动手脚)我们代码第一个Call指令跟随的是一个函数偏移地址。我们用ollydbg打开mspaint.exe这个我们要注入的进程文件
[plain] view
plaincopy
01034BD7 > $ 6A 70 push 70
01034BD9 . 68 00740001 push 01007400
01034BDE . E8 09040000 call 01034FEC
用IDA打开之,可以看到
[plain] view
plaincopy
public _wWinMainCRTStartup
.text:01034BD7 _wWinMainCRTStartup proc near
……
.text:01034BD7 push 70h
.text:01034BD9 push offset stru_1007400
.text:01034BDE call __SEH_prolog
这个特性很重要,我们在找到被注入进程的第一个call指令后,将之后的偏移量记下来,并计算出真实函数的起始地址。我们程序结束后再jmp到这个真实地址。当然不否认这个方案存在很大风险,比如第一call的不是偏移地址,而是一个寄存器中保存的地址,那么我们这个方案就挂了!
我们得到第一个Call指令位置和Call的地址后,我们就可以考虑将我们的代码注入到傀儡中。因为我们这次要在代码中动态地修改注入的代码,于是我们需要使用ShellCode,毕竟汇编和01之间还是隔一层的。ShellCode也很好得到,我们写完汇编后,查看该处的16进制码即可。
[cpp] view
plaincopy
/*
$ ==> > 60 pushad
$+1 > 9C pushfd
$+2 > 68 11111111 push 11111111 //加载的dll名称
$+7 > E8 444288A5 call LoadLibraryW //LoadLibraryW地址
$+C > 50 push eax
$+D > E8 444288A5 call FreeLibrary //FreeLibrary地址
$+12 > 9D popfd
$+13 > 61 popad
$+14 >- E9 495399B6 jmp 33333333 //跳转到第一个call函数开始
*/
BYTE lpShellCode[] = {
0x60,
0x9c,
0x68,0xcc,0xcc,0xcc,0xcc,
0xe8,0xcc,0xcc,0xcc,0xcc,
0x50,
0xe8,0xcc,0xcc,0xcc,0xcc,
0x9d,
0x61,
0xe9,0xcc,0xcc,0xcc,0xcc
};
OK,此处我们预留了4个地址,分别是LoadLibrary加载的DLL的路径的地址,LoadLibrary 和FreeLibrary的地址,以及真实Call函数地址的在ShellCode中的偏移量。下步我们填充这些数据。
[cpp] view
plaincopy
DWORD dwCallAddrOffset = 0;
if( FALSE == ReadProcessMemory( hProcess, lpFirstCallAddr, &dwCallAddrOffset, 4, NULL ) ) {
break;
}
// 计算call的目标函数地址
LPSTR lpRealFuncAddr = lpFirstCallAddr + 4 + dwCallAddrOffset;
DWORD dwDllPathSize = ( (DWORD) wcslen( lpDllPath ) ) * sizeof(WCHAR);
LPVOID lpDllPathAddr = VirtualAllocEx( hProcess, NULL, dwDllPathSize, MEM_COMMIT, PAGE_READWRITE );
if( NULL == lpDllPathAddr ) {
break;
}
if( FALSE == WriteProcessMemory( hProcess, lpDllPathAddr, lpDllPath, dwDllPathSize, NULL) ) {
break;
}
LPLoadLibrary lpFuncLoadLibraryAddr = LoadLibraryW;
LPFreeLibrary lpFuncFreeLibraryAddr = FreeLibrary;
if ( NULL == lpFuncLoadLibraryAddr || NULL == FreeLibrary ) {
break;
}
size_t ShellCodeSize = strlen( (char*)lpShellCode ) + 1;
LPVOID lpShellCodeAddr = VirtualAllocEx( hProcess, NULL, ShellCodeSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE );
if ( NULL == lpShellCodeAddr ) {
break;
}
// 修改shellcode
// 填充DLL路径
memcpy( lpShellCode + 0x03, &lpDllPathAddr, 4 );
// 填充LoadLibrary偏移
DWORD dwCallLoadLibraryOffset = (*(LPDWORD)&lpFuncLoadLibraryAddr) -( (*(LPDWORD)&lpShellCodeAddr) + 0x0C );
memcpy( lpShellCode + 0x08, &dwCallLoadLibraryOffset, 4 );
// 填充FreeLibrary偏移
DWORD dwCallFreeLibraryOffset = (*(LPDWORD)&lpFuncFreeLibraryAddr) - ( (*(LPDWORD)&lpShellCodeAddr) + 0x12 );
memcpy( lpShellCode + 0x0E, &dwCallFreeLibraryOffset, 4 );
// 填充返回地址偏移,即原来的Call地址
DWORD dwRealFuncOffset = (*(LPDWORD)&lpRealFuncAddr) - ( (*(LPDWORD)&lpShellCodeAddr) + 0x19 );
memcpy( lpShellCode + 0x15, &dwRealFuncOffset, 4 );
// 写入shellcode
if( FALSE == WriteProcessMemory( hProcess, lpShellCodeAddr, lpShellCode, ShellCodeSize, NULL ) ) {
break;
}
在我们写入ShellCode后,我们可以拿到ShellCode的地址,然后我们算出它在第一个Call指令的偏移
[cpp] view
plaincopy
//计算call的新地址
DWORD dwNewCallAddrOffset = (*(LPDWORD)&lpShellCodeAddr) - ((*(LPDWORD)&lpFirstCallAddr) + 4 );
之后就要做个非常重要的事情——将新Call地址写入已经载入内存中的傀儡代码中。因为一般PE文件的代码段的内存页是EXECUTE|READ,但是不具备WRITE属性。于是我们要将第一个Call指令所在的内存页的页属性改为PAGE_EXECUTE_READWRITE,我们写入新Call地址后,再将原来的页属性设置回去,善始善终。
[cpp] view
plaincopy
MEMORY_BASIC_INFORMATION stMemBasicInfor = {0};
if ( FALSE == VirtualQueryEx( hProcess, lpFirstCallAddr, &stMemBasicInfor, sizeof(MEMORY_BASIC_INFORMATION) ) )
{
break;
}
DWORD dwOldProtect = 0;
if ( FALSE == VirtualProtectEx( hProcess, stMemBasicInfor.BaseAddress, stMemBasicInfor.RegionSize, PAGE_EXECUTE_READWRITE, &dwOldProtect ) )
{
break;
}
if ( FALSE == WriteProcessMemory( hProcess, lpFirstCallAddr, &dwNewCallAddrOffset, 4, NULL ) )
{
break;
}
VirtualProtectEx( hProcess, stMemBasicInfor.BaseAddress, stMemBasicInfor.RegionSize, dwOldProtect, NULL );
最简单的修改程序入口点进行注入的方法就是如此。
方案我整理出的工程。
(转载请指明出处)
在PE文件中,其中有个字段标识程序入口点位置。我们通过这个字段,到达程序入口点。PE文件的结构我这儿不讨论(我会在之后写关于PE文件的介绍和研究),我只列出一些和程序入口点有关的数据结构
[cpp] view
plaincopy
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
[cpp] view
plaincopy
typedef struct _IMAGE_OPTIONAL_HEADER {
//
// Standard fields.
//
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
DWORD BaseOfData;
//
// NT additional fields.
//
DWORD ImageBase;
……
}
其中ImageBase是程序加载的基址,AddressOfEntryPoint是代码执行的入口偏移。于是我们的程序入口点是
[cpp] view
plaincopy
PIMAGE_DOS_HEADER lpstDosHeader = (PIMAGE_DOS_HEADER)(LPSTR)lpMapFile;
PIMAGE_NT_HEADERS lpstNtHeaders = (PIMAGE_NT_HEADERS)((LPSTR)lpMapFile + lpstDosHeader->e_lfanew );
dwPEEntry = lpstNtHeaders->OptionalHeader.AddressOfEntryPoint + lpstNtHeaders->OptionalHeader.ImageBase;
我们将从程序入口点开始搜索Call这个指令。这个地方存在一个经验,就是一般(如果没动手脚)我们代码第一个Call指令跟随的是一个函数偏移地址。我们用ollydbg打开mspaint.exe这个我们要注入的进程文件
[plain] view
plaincopy
01034BD7 > $ 6A 70 push 70
01034BD9 . 68 00740001 push 01007400
01034BDE . E8 09040000 call 01034FEC
用IDA打开之,可以看到
[plain] view
plaincopy
public _wWinMainCRTStartup
.text:01034BD7 _wWinMainCRTStartup proc near
……
.text:01034BD7 push 70h
.text:01034BD9 push offset stru_1007400
.text:01034BDE call __SEH_prolog
这个特性很重要,我们在找到被注入进程的第一个call指令后,将之后的偏移量记下来,并计算出真实函数的起始地址。我们程序结束后再jmp到这个真实地址。当然不否认这个方案存在很大风险,比如第一call的不是偏移地址,而是一个寄存器中保存的地址,那么我们这个方案就挂了!
我们得到第一个Call指令位置和Call的地址后,我们就可以考虑将我们的代码注入到傀儡中。因为我们这次要在代码中动态地修改注入的代码,于是我们需要使用ShellCode,毕竟汇编和01之间还是隔一层的。ShellCode也很好得到,我们写完汇编后,查看该处的16进制码即可。
[cpp] view
plaincopy
/*
$ ==> > 60 pushad
$+1 > 9C pushfd
$+2 > 68 11111111 push 11111111 //加载的dll名称
$+7 > E8 444288A5 call LoadLibraryW //LoadLibraryW地址
$+C > 50 push eax
$+D > E8 444288A5 call FreeLibrary //FreeLibrary地址
$+12 > 9D popfd
$+13 > 61 popad
$+14 >- E9 495399B6 jmp 33333333 //跳转到第一个call函数开始
*/
BYTE lpShellCode[] = {
0x60,
0x9c,
0x68,0xcc,0xcc,0xcc,0xcc,
0xe8,0xcc,0xcc,0xcc,0xcc,
0x50,
0xe8,0xcc,0xcc,0xcc,0xcc,
0x9d,
0x61,
0xe9,0xcc,0xcc,0xcc,0xcc
};
OK,此处我们预留了4个地址,分别是LoadLibrary加载的DLL的路径的地址,LoadLibrary 和FreeLibrary的地址,以及真实Call函数地址的在ShellCode中的偏移量。下步我们填充这些数据。
[cpp] view
plaincopy
DWORD dwCallAddrOffset = 0;
if( FALSE == ReadProcessMemory( hProcess, lpFirstCallAddr, &dwCallAddrOffset, 4, NULL ) ) {
break;
}
// 计算call的目标函数地址
LPSTR lpRealFuncAddr = lpFirstCallAddr + 4 + dwCallAddrOffset;
DWORD dwDllPathSize = ( (DWORD) wcslen( lpDllPath ) ) * sizeof(WCHAR);
LPVOID lpDllPathAddr = VirtualAllocEx( hProcess, NULL, dwDllPathSize, MEM_COMMIT, PAGE_READWRITE );
if( NULL == lpDllPathAddr ) {
break;
}
if( FALSE == WriteProcessMemory( hProcess, lpDllPathAddr, lpDllPath, dwDllPathSize, NULL) ) {
break;
}
LPLoadLibrary lpFuncLoadLibraryAddr = LoadLibraryW;
LPFreeLibrary lpFuncFreeLibraryAddr = FreeLibrary;
if ( NULL == lpFuncLoadLibraryAddr || NULL == FreeLibrary ) {
break;
}
size_t ShellCodeSize = strlen( (char*)lpShellCode ) + 1;
LPVOID lpShellCodeAddr = VirtualAllocEx( hProcess, NULL, ShellCodeSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE );
if ( NULL == lpShellCodeAddr ) {
break;
}
// 修改shellcode
// 填充DLL路径
memcpy( lpShellCode + 0x03, &lpDllPathAddr, 4 );
// 填充LoadLibrary偏移
DWORD dwCallLoadLibraryOffset = (*(LPDWORD)&lpFuncLoadLibraryAddr) -( (*(LPDWORD)&lpShellCodeAddr) + 0x0C );
memcpy( lpShellCode + 0x08, &dwCallLoadLibraryOffset, 4 );
// 填充FreeLibrary偏移
DWORD dwCallFreeLibraryOffset = (*(LPDWORD)&lpFuncFreeLibraryAddr) - ( (*(LPDWORD)&lpShellCodeAddr) + 0x12 );
memcpy( lpShellCode + 0x0E, &dwCallFreeLibraryOffset, 4 );
// 填充返回地址偏移,即原来的Call地址
DWORD dwRealFuncOffset = (*(LPDWORD)&lpRealFuncAddr) - ( (*(LPDWORD)&lpShellCodeAddr) + 0x19 );
memcpy( lpShellCode + 0x15, &dwRealFuncOffset, 4 );
// 写入shellcode
if( FALSE == WriteProcessMemory( hProcess, lpShellCodeAddr, lpShellCode, ShellCodeSize, NULL ) ) {
break;
}
在我们写入ShellCode后,我们可以拿到ShellCode的地址,然后我们算出它在第一个Call指令的偏移
[cpp] view
plaincopy
//计算call的新地址
DWORD dwNewCallAddrOffset = (*(LPDWORD)&lpShellCodeAddr) - ((*(LPDWORD)&lpFirstCallAddr) + 4 );
之后就要做个非常重要的事情——将新Call地址写入已经载入内存中的傀儡代码中。因为一般PE文件的代码段的内存页是EXECUTE|READ,但是不具备WRITE属性。于是我们要将第一个Call指令所在的内存页的页属性改为PAGE_EXECUTE_READWRITE,我们写入新Call地址后,再将原来的页属性设置回去,善始善终。
[cpp] view
plaincopy
MEMORY_BASIC_INFORMATION stMemBasicInfor = {0};
if ( FALSE == VirtualQueryEx( hProcess, lpFirstCallAddr, &stMemBasicInfor, sizeof(MEMORY_BASIC_INFORMATION) ) )
{
break;
}
DWORD dwOldProtect = 0;
if ( FALSE == VirtualProtectEx( hProcess, stMemBasicInfor.BaseAddress, stMemBasicInfor.RegionSize, PAGE_EXECUTE_READWRITE, &dwOldProtect ) )
{
break;
}
if ( FALSE == WriteProcessMemory( hProcess, lpFirstCallAddr, &dwNewCallAddrOffset, 4, NULL ) )
{
break;
}
VirtualProtectEx( hProcess, stMemBasicInfor.BaseAddress, stMemBasicInfor.RegionSize, dwOldProtect, NULL );
最简单的修改程序入口点进行注入的方法就是如此。
方案我整理出的工程。
(转载请指明出处)
相关文章推荐
- VC下提前注入进程的一些方法3——修改程序入口点
- VC下提前注入进程的一些方法2——远线程带参数
- VC下提前注入进程的一些方法2——远线程带参数
- VC下提前注入进程的一些方法1——远线程不带参数
- VC下提前注入进程的一些方法1——远线程不带参数
- 判断指定的进程或程序是否存在方法小结(vc等)
- 修改VC程序的标题的方法
- 使用VC创建进程和执行命令行程序的方法
- VC提前注入.net软件的方法
- 使用Visual Basic 6.0开发程序的一些方法技巧收集之二:进程操作
- vc++ 向其他进程注入代码的三种方法
- Visual Studio 2017中使用正则修改部分内容 如何使用ILAsm与ILDasm修改.Net exe(dll)文件 C#学习-图解教程(1):格式化数字字符串 小程序开发之图片转Base64(C#、.Net) jquery遍历table为每一个单元格取值及赋值 。net加密解密相关方法 .net关于坐标之间一些简单操作
- 判断指定的进程或程序是否存在方法小结(vc等)
- 让你提前认识软件开发(51):VC++集成开发环境中Linux下Pclint工程的配置方法及常见错误修改
- vc 关闭程序后任务管理器进程中进程仍然存在的解决方法
- 浅议VC程序中进程间信息交换方法
- vc++ 向其他进程注入代码的三种方法
- VC++/MFC程序图标更改方法以及修改程序标题 超级简单啦
- 使用VC创建进程和执行命令行程序的方法
- VC中的一些常用方法